const registerEvent = function (el, event, fn, jQuery = false, options = {}) {
    if (jQuery) {
        $(el).on(event, fn);
    } else {
        el.addEventListener(event, fn, options);
    }
    return el;
}
/**
 * 首字母大写
 * @param str
 * @returns {string}
 */
const firstUpper = function (str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
 * 判断函数
 * @param fn
 * @returns {*}
 */
const isFunction = function (fn) {
    if (typeof fn == 'string' && /^[\s\r\n\t]*function[\s\r\n\t]*\(.*?\)[\s\r\n\t]*\{/.test(fn)) {
        eval('fn=' + fn);
    }
    return fn;
}

/**
 * 触发事件
 * @param el
 * @param event
 * @returns {*}
 */
const triggerEvent = function (el, event) {
    let e = new Event(event);
    el.dispatchEvent(e);
    return e;
}

/**
 * 日期选择框
 * @param el
 * @param input
 * @param options
 */
const datetimePicker = function ({el, input, options}) {
    let opt = options || {
        "format": "YYYY-MM-DD HH:mm:ss",
        "locale": "zh-CN",
        "allowInputToggle": true,
    };
    if (!opt.format) {
        opt.format = "YYYY-MM-DD HH:mm:ss";
    }
    $(el).datetimepicker(opt);
    if (!input) {
        input = el.querySelector('input');
    }
    registerEvent(input, 'focus', (e) => {
        let picker = e.currentTarget.parentElement.querySelector('.bootstrap-datetimepicker-widget');
        if (picker) {
            let rect = picker.getBoundingClientRect();
            picker.style.position = 'fixed';
            picker.style.left = rect.left + 'px';
            picker.style.top = rect.top + 'px';
            picker.style.height = rect.height + 'px';
        }
    });
}

/**
 *
 * @param type
 * @param year
 * @param month
 * @param week
 * @param date
 * @param hour
 * @param minute
 * @param second
 * @returns {{start: (string|string), end: (string|string)}}
 */
const dateRange = function ({type, year, month, week, date, hour, minute, second}) {
    let currentDate = new Date();
    let timestamp = currentDate.getTime();
    let Y = currentDate.getFullYear();
    let M = currentDate.getMonth() + 1;
    let D = currentDate.getDate();
    if (year) timestamp -= (31 * 7 + 4 * 30 + ((Y % 4 === 0 && Y % 100 !== 0) || Y % 100 === 0 ? 29 : 28)) * 24 * 3600 * 1000 * Number(year);
    if (month) {
        let m = 1;
        let dateNum = 0;
        while (m <= Number(month)) {
            let mm = M - m;
            if (mm <= 0) mm = 12 + mm;
            if ([1, 3, 5, 7, 8, 10, 12].includes(mm)) {
                dateNum += 31;
            } else if (mm == 2) {
                dateNum += ((Y % 4 === 0 && Y % 100 !== 0) || Y % 100 === 0 ? 29 : 28);
            } else {
                dateNum += 30;
            }
            m++;
        }
        timestamp -= dateNum * 24 * 3600 * 1000;
    }
    if (date) timestamp -= 24 * 3600 * 1000 * Number(date);
    if (week) timestamp -= 24 * 3600 * 1000 * 7 * Number(week);
    if (hour) timestamp -= 3600 * 1000 * Number(hour);
    if (minute) timestamp -= 60 * 1000 * Number(minute);
    if (second) timestamp -= 1000 * Number(second);
    let startDate = new Date(timestamp);
    return {
        start: type == 'time' ? startDate.toLocaleTimeString() : (type == 'date' ? startDate.toLocaleDateString() : startDate.toLocaleString()),
        end: type == 'time' ? currentDate.toLocaleTimeString() : (type == 'date' ? currentDate.toLocaleDateString() : currentDate.toLocaleString()),
    };
}

/**
 * 发送请求
 * @param url
 * @param method
 * @param data
 * @returns {Promise<unknown>}
 */
const request = function ({url, data, method = 'GET'}) {
    if (['GET', 'OPTIONS', 'HEAD'].includes(method.toLocaleUpperCase()) && data) {
        const searchParams = new URLSearchParams();

        function appendParams(items, name = '') {
            Object.entries(items).forEach(([key, value]) => {
                if (name) key = name + '[' + key + ']';
                if (value && typeof value == 'object') {
                    appendParams(value, key);
                } else {
                    searchParams.append(key, value);
                }
            });
        }

        appendParams(data);

        if (url.indexOf('?') >= 0) {
            url += '&' + searchParams.toString();
        } else {
            url += '?' + searchParams.toString();
        }
        data = null;
    }
    return new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            method: method,
            data: data,
            contentType: false,
            processData: false,
            success(data, state, response) {
                resolve({
                    data: data,
                    response,
                    state,
                });
            },
            fail(err) {
                reject(err);
            },
        })
    });
}

/**
 * 输出提示消息
 * @param message
 * @param type
 */
const alertMsg = function ({message, type = 'info'}) {
    if (toastr) {
        toastr[type](message);
    } else {
        alert(message);
    }
}

/**
 * 表单事件
 */
class FormEvent {
    constructor({column, parent, oldValue, newValue, type, $element, originEvent}) {
        this.column = column;
        this.parent = parent;
        this.oldValue = oldValue;
        this.newValue = newValue;
        this.type = type;
        this.$element = $element;
        this.originEvent = originEvent;
        this.check();
    }

    check() {
        if (this.column instanceof FormGrid) {
            if (this.type) {
                this.call(this.column.events[this.type])
            } else {
                Object.keys(this.column.events || {}).forEach((type) => {
                    this.call(this.column.events[type])
                });
            }
        } else if (this.column.configs?.events) {
            if (this.$element) {
                this.oldValue = this.$element.value || this.$element.dataset.value;
                Object.keys(this.column.configs.events).forEach((event) => {
                    registerEvent(this.$element, event, (e) => {
                        let value = this.$element.value || this.$element.dataset.value;
                        new FormEvent({
                            parent: this.parent,
                            column: this.column,
                            oldValue: this.oldValue,
                            newValue: value,
                            type: this.type,
                            originEvent: e,
                        });
                        this.oldValue = value;
                    });
                })
            } else {
                if (this.type) {
                    this.call(this.column.configs.events[this.type])
                } else {
                    Object.keys(this.column.configs.events || {}).forEach((type) => {
                        this.call(this.column.configs.events[type])
                    })
                }
            }
        }
        return this;
    }

    call(fn) {
        if (fn) {
            if (fn instanceof Array) {
                fn.forEach((vo) => {
                    this.call(vo);
                });
            } else {
                if (typeof fn == "string") {
                    eval('fn=' + fn);
                }
                fn(this);
            }
        }
        return this;
    }
}

class Group {
    constructor({parent, columns, element, row, name, column, value, grid}) {
        this.parent = parent;
        this.columns = columns || [];
        this.row = row;
        this.grid = grid;
        this.name = name;
        this.$element = element;
        this.renders = [];
        this.column = column;
        this.value = value || {};
        this.realValue = JSON.parse(JSON.stringify(this.value));
        this.render();
    }

    onChange(name, value) {
        let oldValue = JSON.parse(JSON.stringify(this.realValue));
        let nameArr = name.replace(this.name, '').split(/\]\[|\]|\[/mg).filter(v => v);
        let lastName = nameArr.pop();
        let newValue = this.realValue;
        nameArr.forEach((n) => {
            if (!newValue[n]) {
                newValue[n] = {};
            }
            newValue = newValue[n];
        });
        newValue[lastName] = value;
        new FormEvent({
            column: this,
            parent: this,
            oldValue,
            newValue: this.realValue,
            type: 'change',
        });
        this.parent.onChange(this.name, this.realValue);
        return this;
    }

    refresh() {
        this.renders.forEach((vo) => {
            vo.name = this.name;
            vo.refresh();
        })
        return this;
    }

    render() {
        this.columns.forEach((column) => {
            this.renders.push(new Render({
                parent: this,
                column,
                value: this.value ? this.value[column.name] || '' : '',
                row: this.row,
                name: this.name,
                el: this.$element,
                label: true,
                grid: this.grid,
            }))
        })
    }
}


class Tab {
    constructor({parent, grid, value, row, el, column, name}) {
        this.parent = parent;
        this.grid = grid;
        this.row = row;
        this.value = value || {};
        this.$element = el;
        this.column = column;
        this.sliderItems = {};
        this.contentItems = {};
        this.$activeSlider = null;
        this.$activeContent = null;
        this.activeName = '';
        this.name = name;
        this.renders = [];
        this.realValue = JSON.parse(JSON.stringify(this.value));
        this.render();
    }

    onChange(name, value) {
        let oldValue = JSON.parse(JSON.stringify(this.realValue));
        let newValue = this.realValue;
        let nameArr = name.replace(this.name, '').split(/\]\[|\]|\[/mg).filter(v => v);
        let lastName = nameArr.pop();
        nameArr.forEach((n) => {
            if (!newValue[n]) {
                newValue[n] = {};
            }
            newValue = newValue[n];
        });
        newValue[lastName] = value;
        new FormEvent({
            column: this.column,
            parent: this,
            oldValue,
            newValue: this.realValue,
            type: 'change',
        });
        this.parent.onChange(this.name, this.realValue);
        return this;
    }

    render() {
        this.renderSlider().renderContent();
    }


    renderSlider() {
        this.$slider = document.createElement('div');
        this.$slider.classList.add('tab-slider');
        this.$element.append(this.$slider);
        this.column.items.forEach((item, index) => {
            this.makeSliderItem(item, index)
        })
        return this;
    }

    makeSliderItem(item, index) {
        let name = item.name || item.title;
        let el = document.createElement('div');
        el.classList.add('tab-slider-item');
        if ((name == this.column.default) || (!this.column.default && index == 0)) {
            el.classList.add('active');
            this.$activeSlider = el;
            this.activeName = name;
        }
        el.innerHTML = item.title;
        this.$slider.append(el);
        this.sliderItems[name] = el;
        registerEvent(el, 'click', (e) => {
            if (this.activeName != name) {
                this.setActive(name);
                new FormEvent({
                    parent: this,
                    column: this.column,
                    oldValue: this.activeName,
                    newValue: name,
                    type: 'change',
                    originEvent: e,
                });
            }
        });
        return this;
    }


    renderContent() {
        this.$content = document.createElement('div');
        this.$content.classList.add('tab-content');
        this.$element.append(this.$content);
        this.column.items.forEach((item, index) => {
            this.makeContentItem(item, index);
        });
        if (!this.$activeContent) {
            this.setActive(Object.keys(this.contentItems)[0]);
        }
        return this;
    }

    makeContentItem(item, index) {
        let name = item.name || item.title;
        let el = document.createElement('div');
        el.classList.add('tab-content-item');
        if (name == this.column.default || (!this.column.default && index == 0)) {
            el.classList.add('active');
            this.$activeContent = el;
            this.$content.append(el);
        } else {
            this.grid.$hideElement.append(el);
        }
        this.contentItems[name] = el;
        item.columns.forEach((column) => {
            this.renders.push(new Render({
                parent: this,
                column,
                value: item.name ? (this.value[item.name] ? this.value[item.name][column.name] : '') : (this.value[column.name] || ''),
                row: this.row,
                name: this.getName(item),
                el,
                label: true,
                grid: this.grid,
                tabItem: item,
                tabIndex: index,
            }))
        });
        return this;
    }

    getName(item) {
        return item.name ? this.name + '[' + item.name + ']' : this.name
    }

    refresh() {
        this.renders.forEach((vo) => {
            vo.name = this.getName(vo.tabItem);
            vo.refresh();
        })
        return this;
    }

    setActive(name) {
        this.$activeSlider.classList.remove('active');
        this.activeName = name;
        this.$activeSlider = this.sliderItems[name];
        this.$activeSlider?.classList.add('active');
        // todo:: 内容
        if (this.$activeContent) {
            this.$activeContent.classList.remove('active');
            this.grid.$hideElement.append(this.$activeContent);
        }
        this.$activeContent = this.contentItems[name];
        this.$activeContent.classList.add('active');
        this.$content.append(this.$activeContent);
        return this;
    }
}


class ImageField {
    parent = null;
    files = [];
    progress = 0;
    name = null;

    constructor(parent) {
        this.parent = parent;
        this.init().render();
    }

    getName() {
        if (!this.name) {
            this.name = (this.parent.name ? this.parent.name + '[' + this.parent.column.name + ']' : this.parent.column.name) + (this.parent.column.configs.multiple ? '[]' : '');
        }
        return this.name;
    }

    refresh() {
        this.$defaultInput.name = this.getName();
        this.files.forEach((vo, i) => {
            let input = vo.$prev.querySelector('input[name]');
            if (input) input.name = this.getName();
        });
        return this;
    }

    init() {
        this.$defaultInput = document.createElement('input');
        this.$defaultInput.type = 'hidden';
        this.$defaultInput.name = this.getName();

        this.$el = document.createElement('div');
        this.$el.classList.add('input-group');
        this.$el.classList.add('form-grid-upload');


        this.$box = document.createElement('div');
        this.$box.classList.add('form-control');
        this.$el.append(this.$box);

        this.$button = document.createElement('div');
        this.$button.classList.add('upload-button');
        this.$button.innerHTML = `<svg t="1656404865959" class="icon" viewBox="0 0 1297 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2675" width="32" height="32"><path d="M759.466667 0c60.3136 0 110.4896 44.066133 113.5616 100.727467l0.170666 5.5296v116.394666a28.672 28.672 0 0 1-57.173333 3.345067l-0.2048-3.345067v-116.394666c0-25.053867-22.254933-46.626133-51.643733-48.7424l-4.676267-0.170667H350.139733c-30.071467 0-53.828267 20.343467-56.149333 44.987733l-0.170667 3.925334v168.106666a28.672 28.672 0 0 1-28.672 28.672l-0.477866-0.034133-0.4096 0.034133H115.234133A57.344 57.344 0 0 0 58.026667 356.113067l-0.136534 4.3008v548.864a57.344 57.344 0 0 0 53.077334 57.207466l4.266666 0.170667h911.7696a57.344 57.344 0 0 0 57.207467-53.077333l0.136533-4.3008V534.528a28.672 28.672 0 0 1 57.173334-3.345067l0.2048 3.345067v374.784c0 61.44-48.298667 111.616-108.987734 114.5856l-5.7344 0.136533H115.234133c-61.44 0-111.616-48.298667-114.5856-108.987733l-0.136533-5.7344V360.448c0-61.44 48.298667-111.616 108.987733-114.5856l5.7344-0.136533 121.207467-0.034134V106.257067c0-57.4464 48.298667-103.3216 107.895467-106.120534l5.802666-0.136533h409.361067z m-188.347734 365.806933c42.2912 0 83.1488 10.205867 119.808 29.457067l3.208534 1.9456a28.672 28.672 0 0 1 9.693866 35.1232 54.545067 54.545067 0 0 0 68.266667 73.352533h-0.034133l3.584-1.024a28.672 28.672 0 0 1 32.324266 16.5888 258.116267 258.116267 0 1 1-236.8512-155.4432z m0 57.344a200.772267 200.772267 0 1 0 193.3312 146.5344l-1.194666-3.9936-1.774934 0.2048-7.714133 0.273067a111.9232 111.9232 0 0 1-111.138133-125.3376l0.648533-4.266667-5.461333-2.048a199.918933 199.918933 0 0 0-56.866134-11.093333z m541.934934-320.853333a28.672 28.672 0 0 1 28.672 28.672v114.688l114.688 0.034133a28.672 28.672 0 0 1 0 57.344h-114.688v114.722134a28.672 28.672 0 0 1-57.344 0V303.035733h-114.756267a28.672 28.672 0 0 1 0-57.344l114.722133-0.034133v-114.688a28.672 28.672 0 0 1 28.672-28.672z" fill="#3586FF" p-id="2676"></path></svg>`;
        this.$button.append(this.$defaultInput);
        this.$box.append(this.$button);


        this.$uploadButton = document.createElement('input');
        this.$uploadButton.type = 'file';
        this.$uploadButton.classList.add('upload-input');
        this.$uploadButton.accept = this.parent.column.attributes.accept || 'image/*';
        if (this.parent.column.configs.multiple) {
            this.$uploadButton.setAttribute('multiple', '');
        }
        if (this.parent.column.attributes.required) {
            this.$el.insertBefore(this.parent.makeAsterisk(), this.$box);
            this.$uploadButton.setAttribute('required', '');
        }
        if (this.parent.column.attributes.disabled) {
            this.$uploadButton.setAttribute('disabled', '');
        }
        if (this.parent.column.attributes.readonly) {
            this.$uploadButton.setAttribute('readonly', '');
        }
        this.$button.append(this.$uploadButton);

        // 上传中状态
        this.$loading = document.createElement('div');
        this.$loading.classList.add('upload-progress');
        const progress = document.createElement('div');
        progress.classList.add('upload-progress-active');
        this.$loading.append(progress);
        // 上传失败状态
        this.$fail = document.createElement('div');
        this.$fail.classList.add('upload-fail');
        const failIcon = document.createElement('i');
        failIcon.classList.add('fa');
        failIcon.classList.add('fa-exclamation');
        this.$fail.append(failIcon);
        const btn = document.createElement('a');
        btn.classList.add('re-upload');
        btn.innerHTML = '重传';
        this.$fail.append(btn);


        if (this.parent.value) {
            if (this.parent.column.configs.multiple) {
                Object.values(typeof this.parent.value == 'string' ? [this.parent.value] : this.parent.value).forEach((url) => {
                    this.files.push({
                        url: url,
                        prev: null,
                        error: null,
                        state: true,
                    });
                })
            } else {
                let url = typeof this.parent.value == 'string' ? this.parent.value : Object.values(this.parent.value)[0];
                if (url) {
                    this.files = [{
                        url: url,
                        prev: null,
                        error: null,
                        state: true,
                    }];
                }
            }
        }
        return this;
    }

    getObjectURL(file) {
        var url = null;
        //下面函数执行效果是一样的，只是针对不同的浏览器执行不同的js函数而已
        if (window.createObjectURL != undefined) { //basic
            url = window.createObjectURL(file);
        } else if (window.URL != undefined) { //mozilla(firefox)
            url = window.URL.createObjectURL(file);
        } else if (window.webkitURL != undefined) { //webkit or chrome
            url = window.webkitURL.createObjectURL(file);
        }
        return url;
    }


    renderPreview() {
        this.files.forEach((vo, i) => {
            if (!vo.$prev) {
                vo.$prev = document.createElement('div');
                vo.$prev.classList.add('form-grid-preview-image');
                let img = document.createElement('img');
                img.src = vo.url;
                vo.$prev.append(img);
                vo.$input = document.createElement('input');
                vo.$input.name = this.getName();
                vo.$input.type = 'text';
                vo.$input.style.display = 'none';
                if (this.parent.column.attributes.required) {
                    vo.$input.setAttribute('required', '');
                }
                if (vo.state === true && vo.state == 1) {
                    vo.$prev.append(vo.$input);
                    vo.$input.value = vo.url;
                } else {
                    vo.$uploading = this.$loading.cloneNode(true);
                    vo.$prev.append(vo.$uploading);
                }
                let delBtn = document.createElement('i');
                delBtn.classList.add('fa');
                delBtn.classList.add('fa-minus-circle');
                delBtn.classList.add('delete')
                vo.$prev.append(delBtn);
                registerEvent(img, 'click', () => {
                    this.previewImage(vo);
                });
                registerEvent(delBtn, 'click', (e) => {
                    const remove = () => {
                        this.files.splice(i, 1);
                        vo.$prev.remove();
                        if (!this.parent.column.configs.multiple && !this.files.length) {
                            this.$box.append(this.$button);
                        }
                        this.parent.change(this.files);
                        this.unlinkFile(vo);
                    }
                    let before = this.parent?.column?.configs?.before_remove;
                    if (before) {
                        if (typeof before == 'string') {
                            eval(`before = ${before};`);
                        }
                        if (typeof before == 'function') {
                            let res = before.call(this, vo, i, this.files);
                            if (res instanceof Promise) {
                                res.then((res) => {
                                    if (res || res === undefined) remove();
                                }).catch(() => {
                                })
                            } else if (res) {
                                remove();
                            }
                        } else {
                            remove();
                        }
                    } else {
                        remove();
                    }

                });
                this.$box.insertBefore(vo.$prev, this.$button);
            }

        })
        if (this.files.length && !this.parent.column.configs.multiple) {
            this.$button.remove();
        } else {
            this.$box.append(this.$button);
        }
        return this;
    }

    previewImage(file) {
        var img;
        if (this.$prevImage) {
            img = this.$prevImage.querySelector('img');
        } else {
            this.$prevImage = document.createElement('section');
            this.$prevImage.classList.add('form-grid-image-preview-shade');
            let zIndex = 9999999999999999999;
            this.$prevImage.style = `position:fixed;`
                + `top:0px;`
                + `left:0px;`
                + `z-index:` + zIndex + `;`
                + `width:100%;`
                + `height:100%;`
                + `display:flex;`
                + `align-items: center;`
                + `justify-content: center;`
                + `background: rgba(0, 0, 0, .4);`;
            let box = document.createElement('div');
            box.classList.add('form-grid-image-preview');
            box.style = `position:relative;display:flex;justify-content: center;align-items: center;`;
            this.$prevImage.append(box);
            img = document.createElement('img');
            img.style = `max-width:80%;max-height: 90%;`;
            box.append(img);
            let closeBtn = document.createElement('i');
            closeBtn.classList.add('fa');
            closeBtn.classList.add('fa-close');
            closeBtn.style = `position:absolute;top:5vh;right:5vh;font-size:200%;color:white;cursor:pointer;`;
            registerEvent(closeBtn, 'click', () => {
                this.$prevImage.remove();
            });
            registerEvent(this.$prevImage, 'click', (e) => {
                if (e.target.querySelector('.form-grid-image-preview')) {
                    this.$prevImage.remove();
                }
            })
            this.$prevImage.append(closeBtn);
        }
        img.src = file.url;
        const win = window.parent || window;
        win.document.body.append(this.$prevImage);
        return this;
    }

    unlinkFile(file) {
        if (file.state === 1) {
            const form = new FormData();
            form.append('_token', this.parent.grid.token);
            form.append('path', this.parent.column.configs.savePath || '');
            form.append('disk', this.parent.column.configs.disk || '');
            form.append('url', file.url);
            request({
                url: this.parent.column.configs.url || this.parent.grid.uploadUrl,
                method: 'post',
                data: form,
            }).then(() => {
            }).catch(() => {
            });
        }
        return this;
    }

    upload(e) {
        if (!e.currentTarget.files.length) {
            return;
        }
        this.progress = 0;
        let n = 0;
        let items = Array.from(e.currentTarget.files);
        items.forEach((vo) => {
            let file = {
                url: this.getObjectURL(vo),
                prev: null,
                error: null,
                state: 0,
                file: vo,
                event: e,
            };
            this.files.push(file);
            this.uploadAction(file).finally(() => {
                n++;
                this.progress = Math.round(n / items.length * 100);
                this.uploaded(file);
            });
        });
        e.currentTarget.value = '';
        this.renderPreview();
        return this;
    }

    /**
     * 上传完成处理
     * @param file
     * @returns {ImageField}
     */
    uploaded(file) {
        if (file.$uploading) {
            file.$uploading.remove();
        }
        if (file.state == 1 || file.state === true) {
            // 上传成功
            file.$input.value = file.url;
            file.$prev.append(file.$input);

            if (this.parent.column.configs.afterUpload instanceof Function) {
                this.parent.column.configs.afterUpload.call(this, file);
            }
            if (this.progress == 100) {
                this.parent.change(this.files);
            }
        } else {
            // 上传失败
            if (!file.$fail) {
                file.$fail = this.$fail.cloneNode(true);
                file.$fail.addEventListener('click', (e) => {
                    if (e.target.classList.value.indexOf('re-upload') >= 0) {
                        file.$fail.remove();
                        file.$prev.append(file.$uploading);
                        this.uploadAction(file).finally(() => {
                            this.uploaded(file);
                        });
                    }
                })
            }
            file.$prev.append(file.fail);
        }
        return this;
    }

    isImageUrl(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => {
                // 如果图片加载成功，则认为URL指向图片
                resolve(url);
            };
            img.onerror = () => {
                // 如果图片加载失败（可能是非图片URL或其他错误），则认为URL不指向图片
                reject(url);
            };
            // 设置图片的src属性，开始加载图片
            img.src = url;
        });
    }

    uploadAction(file) {
        return new Promise((resolve, reject) => {
            const verifyUrl = (url) => {
                if (url && typeof url == "object") {
                    url = url.url || url.data || url.content || url.message;
                }
                this.isImageUrl(url).then((src) => {
                    file.url = src;
                    file.state = 1;
                    resolve(file);
                }).catch((err) => {
                    reject(err);
                })
            }
            if (this.parent.column.configs.beforeUpload instanceof Function) {
                let resp = this.parent.column.configs.beforeUpload.call(this, file);
                if (resp instanceof Promise) {
                    resp.then((res) => {
                        verifyUrl(res);
                    }).catch((err) => {
                        reject(err);
                    })
                } else if (resp) {
                    verifyUrl(resp);
                } else {
                    reject('url为空');
                }
            } else {
                const form = new FormData();
                form.append('file', file.file);
                form.append('_token', this.parent.grid.token);
                form.append('path', this.parent.column.configs.savePath || '');
                form.append('disk', this.parent.column.configs.disk || '');
                request({
                    url: this.parent.column.configs.url || this.parent.grid.uploadUrl,
                    method: 'post',
                    data: form,
                }).then((url) => {
                    verifyUrl(url);
                }).catch((err) => {
                    reject(err);
                });
            }
        }).catch((err) => {
            file.error = err;
            file.state = 2;
        });
    }

    render() {
        this.parent.$element.append(this.$el);
        this.renderPreview();
        this.parent.makeAttributes({el: this.$el}).insertLabel(this.$el)
        registerEvent(this.$uploadButton, 'change', (e) => {
            this.upload(e);
        })
        return this;
    }
}

/**
 * 渲染器
 */
class Render {
    /**
     * 渲染器构造函数
     * @param parent
     * @param column
     * @param value
     * @param row
     * @param name
     * @param el
     * @param label
     * @param grid
     * @param tabItem
     * @param tabIndex
     */
    constructor({parent, column, value, row, name, el, label, grid, tabItem, tabIndex}) {
        this.parent = parent;
        this.column = column;
        this.value = value;
        this.row = row;
        this.name = name;
        this.$element = el;
        this.label = label;
        this.grid = grid;
        this.tabItem = tabItem;
        this.tabIndex = tabIndex;
        this.nodes = [];
        this.realValue = value ? (typeof value == 'object' ? JSON.parse(JSON.stringify(value)) : value) : null;
        this.column.__render_name__ = this.getName();
        if (this[column.type] instanceof Function) {
            this[column.type]();
        } else if (this['render' + firstUpper(column.type)] instanceof Function) {
            this['render' + firstUpper(column.type)]();
        } else {
            console.error(column.type + ' is not found!');
        }
        this.onChange();
    }

    /**
     * 触发值变更事件
     * @param e
     * @param i
     * @returns {Render}
     */
    change(e, i = 0) {
        let oldValue = this.realValue;
        let newValue = e.currentTarget?.value;
        if (this.column.type == 'range') {
            newValue = Object.assign({}, oldValue);
            if (i == 1) {
                newValue.end = e.currentTarget.value;
            } else {
                newValue.start = e.currentTarget.value;
            }
        } else if (this.column.type == 'color') {
            newValue = e.currentTarget.querySelector('input')?.value;
        } else if (this.column.type == 'image') {
            if (this.column.configs.multiple) {
                oldValue = [].concat(this.realValue || []);
                newValue = e.map((vo) => {
                    return vo.url;
                });
            } else {
                newValue = e[0]?.url || '';
            }
        }
        new FormEvent({
            column: this.column,
            parent: this,
            oldValue: oldValue,
            newValue: newValue,
            type: 'change',
            originEvent: e,
        })
        this.realValue = newValue;
        this.parent.onChange(this.getName(), this.realValue);
        return this;
    }

    /**
     * 变化事件
     * @returns {Render}
     */
    onChange() {
        if (!['modal', 'expand', 'group', 'tab', 'grid', 'html', 'display', 'switch', 'color', 'image'].includes(this.column.type)) {
            this.nodes.forEach((node, i) => {
                if (['date', 'datetime', 'time'].includes(this.column.type) || (this.column.type == 'range' && ['date', 'datetime', 'time'].includes(this.column.configs.type))) {
                    registerEvent(node, 'blur', (e) => {
                        this.change(e, i);
                    });
                } else if (['decimal', 'select'].includes(this.column.type)) {
                    registerEvent(node, 'change', (e) => {
                        this.change(e, i);
                    }, true);
                } else {
                    registerEvent(node, 'change', (e) => {
                        this.change(e, i);
                    });
                }
            });
        } else if (arguments.length) {
            this.parent.onChange(...arguments);
        }

        return this;
    }

    /**
     * 刷新
     * @returns {Render}
     */
    refresh() {
        switch (this.column.type) {
            case 'display':
            case 'html':
                break;
            case 'modal':
            case 'expand':
            case 'tab':
            case 'group':
            case 'grid':
            case 'image':
                this.nodes.forEach((vo) => {
                    vo.name = this.getName();
                    vo.refresh();
                })
                break;
            case 'range':
                this.nodes[0].name = this.getName('start');
                this.nodes[1].name = this.getName('end');
                break;
            default:
                this.nodes.forEach((vo) => {
                    vo.name = this.getName();
                })
        }
        new FormEvent({
            column: this.column,
            parent: this,
            oldValue: this.value,
            newValue: this.realValue,
            type: 'refresh',
        });
        return this;
    }

    /**
     * 获取元素名称
     * @param name
     * @returns {null|string|*|string}
     */
    getName(name = null) {
        switch (this.column.type) {
            case 'checkbox':
                if (this.name) {
                    if (this.column.name) {
                        return this.name + '[' + this.column.name + ']' + (name ? '[' + name + ']' : '') + '[]';
                    }
                    return this.name + (name ? '[' + name + ']' : '') + '[]';
                } else if (this.column.name) {
                    return this.column.name + (name ? '[' + name + ']' : '') + '[]'
                } else {
                    return name ? name + '[]' : '';
                }
            default:
                if (this.name) {
                    if (this.column.name) {
                        return this.name + '[' + this.column.name + ']' + (name ? '[' + name + ']' : '');
                    }
                    return this.name + (name ? '[' + name + ']' : '');
                } else if (this.column.name) {
                    return this.column.name + (name ? '[' + name + ']' : '');
                } else {
                    return name ? name : '';
                }
        }
    }


    /**
     * 必填标志
     * @returns {HTMLSpanElement}
     */
    makeAsterisk() {
        let asterisk = document.createElement('span');
        asterisk.classList.add('asterisk');
        return asterisk;
    }

    /**
     * 检查属性
     * @param el
     * @param input
     * @returns {Render}
     */
    makeAttributes({el, input}) {
        if (input) {
            if (this.column.attributes.required) {
                input.parentElement.insertBefore(this.makeAsterisk(), input);
                input.setAttribute('required', '');
            }
            if (this.column.attributes.disabled) {
                input.setAttribute('disabled', '');
            }
            if (this.column.attributes.readonly) {
                input.setAttribute('readonly', '');
            }
            if (this.column.attributes.placeholder) {
                input.setAttribute('placeholder', this.column.attributes.placeholder);
            }
        }
        if (el) {
            for (let i in this.column.attributes) {
                if (['required', 'disabled', 'readonly', 'placeholder'].includes(i)) {
                    continue;
                }
                let v = this.column.attributes[i];
                if (typeof v == 'object') {
                    if (i == 'style') {
                        let str = '';
                        for (let name in v) {
                            str = name + ':' + v[name] + ';';
                        }
                        v = str;
                    } else {
                        v = JSON.stringify(v);
                    }
                }
                el.setAttribute(i, v);
            }
        }
        return this;
    }

    /**
     * 生成label元素并返回
     * @returns {HTMLLabelElement}
     */
    makeLabel() {
        let labelWidth = this.column.configs?.labelWidth || this.getParentConfigs().labelWidth || 6;
        let label = document.createElement('label');
        label.innerHTML = this.column.label;
        label.classList.add('col-' + labelWidth);
        return label;
    }

    /**
     * 获取父级配置
     * @returns {*|{}}
     */
    getParentConfigs() {
        if (this.parent instanceof Group) {
            if (this.parent.parent instanceof Render) {
                return this.parent.parent.parent.configs || {};
            }
            return this.parent.parent.configs || {};
        }
        return this.parent.column.configs || {};
    }

    /**
     * 插入label元素
     * @param ele
     * @returns {Render}
     */
    insertLabel(ele) {
        if (this.label && this.column.label && !this.column.configs?.disabledLabel) {
            let parentNode = ele.parentElement;
            let label = this.makeLabel()
            let col = document.createElement('div');
            col.classList.add('form-grid-col');
            col.classList.add('flex');
            col.append(label);
            col.append(ele);
            parentNode.append(col);
            if (['grid'].includes(this.column.type)) {
                label.classList.value = '';
                col.classList.add('col-24');
                let labelWidth = label.getBoundingClientRect().width;
                ele.style.width = 'calc(100% - ' + labelWidth + 'px)';
                col.style['align-items'] = 'flex-start';
            } else {
                if (['expand', 'modal', 'group', 'tabRow', 'tab'].includes(this.parent.column.type)) {
                    let w = this.tabItem?.configs.columnWidth || this.parent.column.configs.columnWidth || 6;
                    col.classList.add('col-' + w);
                }
                if (ele.dataset.colWidth === 'false') {
                    label.classList.value = '';
                } else {
                    let width = this.column.configs.width || this.getParentConfigs().width || 18;
                    ele.classList.add('col-' + width);
                }
            }
        } else {
            let width = 24;
            if (['expand', 'modal', 'group', 'tabRow', 'tab'].includes(this.parent.column?.type)) {
                width = this.tabItem?.configs.columnWidth || this.parent.column.configs.columnWidth || 6;
                ele.style.padding = '5px';
            }
            ele.classList.add('col-' + width);
        }
        if (this.column.configs?.help && !(this.parent instanceof Row)) {
            let helper = document.createElement('span');
            helper.classList.add('help-block');
            helper.classList.add('form-grid-help-block');
            helper.innerHTML = `<i class="fa fa-info-circle"></i><text>${this.column.configs.help}</text>`;
            helper.style.position = 'relative';
            helper.style.float = 'left';
            ele.append(helper);
        }
        return this;
    }

    /**
     * 输入框
     * @returns {Row}
     */
    input() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        // el.style = 'width: 100%;position: relative;';
        this.$element.append(el);
        let input = document.createElement('input');
        input.type = this.column.attributes.type || 'text';
        input.name = this.getName();
        input.value = this.value;
        input.classList.add('form-control');
        input.classList.add('text');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 显示框
     * @returns {Render}
     */
    display() {
        let el = document.createElement('div');
        el.classList.add('display')
        el.classList.add('field-item--' + this.column.type);
        el.classList.add(this.column.name);
        el.innerHTML = this.value;
        this.$element.append(el);
        this.nodes.push(el);
        return this.makeAttributes({el}).insertLabel(el);
    }

    /**
     * 下拉选择框
     * @returns {Row}
     */
    select() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        el.classList.add('form-grid-select')
        this.$element.append(el);
        let select = document.createElement('select');
        select.classList.add('form-control');
        select.classList.add('select2-hidden-accessible');
        select.classList.add('field-item--' + this.column.type);
        select.classList.add(this.column.name);
        select.name = this.getName();
        select.setAttribute('aria-hidden', "true");
        select.setAttribute('tabindex', '-1');
        if (this.column.configs.multiple) {
            select.setAttribute('multiple', '');
        } else {
            let opt = document.createElement('option');
            opt.value = '';
            select.append(opt);
        }
        this.nodes.push(select);

        if (this.column.configs.options) {
            let options = isFunction(this.column.configs.options);
            if (typeof options == 'function') {
                options = options.call(this);
            }
            for (let i in options) {
                let opt = document.createElement('option');
                if (i == this.value || (this.value instanceof Array && this.value.includes(i))) {
                    opt.setAttribute('selected', 'selected');
                }
                opt.value = i;
                opt.innerHTML = options[i];
                select.append(opt);
            }
        }
        el.append(select);
        $(select).select2(this.formatSelectOption(this.column));
        if (this.column.attributes.readonly) {
            el.classList.add('disabled');
        }
        registerEvent(el, 'click', () => {
            let el = document.querySelector('.select2-container--open .wen-form-grid-select--open');
            if (el && !el.dataset.zIndex) {
                el.dataset.zIndex = this.grid.getMaxZIndex();
                el.parentElement.style['z-index'] = el.dataset.zIndex;
            }
        });
        return this.makeAttributes({el, input: select}).insertLabel(el);
    }

    /**
     * 格式化自定select2配置选项
     * @param column
     * @returns {{placeholder: {id: string, text: string}, allowClear: boolean}}
     */
    formatSelectOption(column) {
        let selectOption = {
            "allowClear": true,
            "placeholder": {
                "id": "",
                "text": '请选择' + column.label
            },
        };

        if (column.attributes.selectOption) {
            for (let i in column.attributes.selectOption) {
                if (['containerCssClass', 'dropdownCssClass'].includes(i)) {
                    continue;
                }
                let val = column.attributes.selectOption[i];
                selectOption[i] = isFunction(val);
            }
        }

        selectOption.containerCssClass = () => {
            let classes = ['wen-form-grid-select'];
            if (column.attributes.selectOption?.containerCssClass) {
                let containerCssClass = isFunction(column.attributes.selectOption.containerCssClass);
                if (typeof containerCssClass == "function") {
                    containerCssClass = containerCssClass.call(this);
                }
                if (typeof containerCssClass == 'string') {
                    containerCssClass = containerCssClass.split(/\s+/mg);
                } else {
                    containerCssClass = Object.values(containerCssClass);
                }
                classes = classes.concat(containerCssClass);
            }
            return classes.join(' ');
        }
        selectOption.dropdownCssClass = () => {
            let classes = ['wen-form-grid-select--open'];
            if (column.attributes.selectOption?.dropdownCssClass) {
                let dropdownCssClass = isFunction(column.attributes.selectOption.dropdownCssClass);
                if (typeof dropdownCssClass == 'function') {
                    dropdownCssClass = dropdownCssClass.call(this);
                }
                if (typeof dropdownCssClass == 'string') {
                    dropdownCssClass = dropdownCssClass.split(/\s+/mg);
                } else {
                    dropdownCssClass = Object.values(dropdownCssClass);
                }
                classes = classes.concat(dropdownCssClass);
            }
            return classes.join(' ');
        }
        return selectOption;
    }

    /**
     * 双精度输入框
     * @returns {Row}
     */
    decimal() {
        let el = document.createElement('div');
        el.classList.add('input-group')
        this.$element.append(el);
        let input = document.createElement('input');
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.type = 'text';
        input.value = this.value;
        input.name = this.getName();
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        $(input).inputmask({"alias": "decimal", "rightAlign": true});
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 数字输入框
     * @returns {Render}
     */
    number() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        el.classList.add('multiple-column-number');
        el.style.minWidth = '150px';
        this.$element.append(el);
        let input = document.createElement('input');
        input.type = 'text';
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.name = this.getName();
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        this.makeAttributes({el, input})
        $(input).addClass('initialized').bootstrapNumber({
            upClass: 'success',
            downClass: 'primary',
            center: true
        });
        input = el.querySelector('input[name]');
        input.value = this.value;
        // todo::number好像会删除原来的input
        this.nodes.push(input);
        el.querySelectorAll('.input-group-btn').forEach((el) => {
            el.addEventListener('click', () => {
                input.dispatchEvent(new Event('change'));
            })
        })
        return this.insertLabel(el);
    }

    /**
     * 文本输入框
     * @returns {Row}
     */
    textarea() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        this.$element.append(el);
        let input = document.createElement('textarea');
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.rows = this.column.attributes.rows || 1;
        input.value = this.value;
        input.name = this.getName();
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 单选框
     * @returns {Render}
     */
    radio() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        el.classList.add('from-grid-radio');
        this.$element.append(el);
        if (this.column.attributes.required) {
            el.append(this.makeAsterisk());
        }
        if (this.column.configs.options) {
            for (let i in this.column.configs.options) {
                let opt = document.createElement('span');
                opt.classList.add('radio-option');
                el.append(opt);
                let input = document.createElement('input');
                input.type = 'radio';
                input.name = this.getName();
                input.value = i;
                input.id = this.name.replace(/\[|\]/img, '-') + this.column.name + '-' + i;
                if (i == this.value) {
                    input.checked = true;
                }
                opt.append(input);
                let label = document.createElement('label');
                label.innerHTML = this.column.configs.options[i];
                label.setAttribute('for', input.id);
                opt.append(label);
                if (this.column.attributes.disabled) {
                    input.setAttribute('disabled', '');
                }
                if (this.column.attributes.readonly) {
                    input.setAttribute('readonly', '');
                    registerEvent(input, 'change', function () {
                        input.checked = !input.checked;
                    });
                }
                if (this.column.attributes.required) {
                    input.setAttribute('required', '');
                }
                this.nodes.push(input);
            }
        }
        return this.makeAttributes({el}).insertLabel(el);
    }

    /**
     * 复选框
     * @returns {Render}
     */
    checkbox() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        el.classList.add('from-grid-checkbox');
        this.$element.append(el);
        if (this.column.attributes.required) {
            el.append(this.makeAsterisk());
        }
        if (this.column.configs.options) {
            for (let i in this.column.configs.options) {
                let opt = document.createElement('span');
                opt.classList.add('checkbox-option');
                el.append(opt);
                let input = document.createElement('input');
                input.type = 'checkbox';
                input.name = this.getName();
                input.value = i;
                input.id = this.name.replace(/\[|\]/img, '-') + this.column.name + '-' + i;
                if (i == this.value) {
                    input.checked = true;
                }
                opt.append(input);
                let label = document.createElement('label');
                label.innerHTML = this.column.configs.options[i];
                label.setAttribute('for', input.id);
                opt.append(label);
                if (this.column.attributes.disabled) {
                    input.setAttribute('disabled', '');
                }
                if (this.column.attributes.readonly) {
                    input.setAttribute('readonly', '');
                    registerEvent(input, 'change', function () {
                        input.checked = !input.checked;
                    });
                }
                if (this.column.attributes.required) {
                    input.setAttribute('required', '');
                }
                this.nodes.push(input);
            }
        }
        return this.makeAttributes({el}).insertLabel(el);
    }

    /**
     * 颜色选择
     * @returns {Row}
     */
    color() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        el.classList.add('colorpicker-element');
        this.$element.append(el);
        let icon = document.createElement('span');
        icon.classList.add('input-group-addon');
        icon.innerHTML = `<i style="background-color: ${this.value};"></i>`;
        el.append(icon);
        let input = document.createElement('input');
        input.type = 'text';
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.name = this.getName();
        input.value = this.value;
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        if (this.column.attributes.readonly) {
            el.classList.add('disabled');
        }
        $(el).colorpicker(this.column.configs.options || []).on('changeColor', (e) => {
            this.change(e);
        });
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 隐藏选项
     * @returns {Render}
     */
    hidden() {
        let input = document.createElement('input');
        input.type = 'hidden';
        input.name = this.getName();
        input.value = this.value;
        this.$element.append(input);
        this.nodes.push(input);
        return this.makeAttributes({input, el: input});
    }

    /**
     * 日期选择
     * @returns {Row}
     */
    date() {
        return this.datetime();
    }

    /**
     * 时间选择
     * @returns {Row}
     */
    time() {
        return this.datetime();
    }

    /**
     * 时间日期选择
     * @returns {Row}
     */
    datetime() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        this.$element.append(el);
        let box = document.createElement('div');
        box.classList.add('input-group');
        el.append(box);
        let icon = document.createElement('span');
        icon.classList.add('input-group-addon');
        icon.innerHTML = `<i class="fa fa-calendar fa-fw"></i>`;
        box.append(icon);
        let input = document.createElement('input');
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.type = 'text';
        input.name = this.getName();
        input.value = this.value;
        input.placeholder = '请选择' + this.column.label;
        box.append(input);
        datetimePicker({
            el: box,
            input,
            options: this.column.configs.options || {
                "format": 'YYYY-MM-DD HH:mm:ss',
                "locale": "zh-CN",
                "allowInputToggle": true,
            },
        })
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 开关
     * @returns {Render}
     */
    switch() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        this.$element.append(el);
        let input = document.createElement('input');
        input.type = 'checkbox';
        input.classList.add('la_checkbox');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.name = this.getName();
        if (this.value == 'on' || this.value === true || (typeof this.value == 'number' && this.value)) {
            input.value = 'on';
            input.setAttribute('checked', '');
        } else {
            input.value = 'off';
        }
        el.append(input);
        this.makeAttributes({el, input});
        let options = Object.assign({}, this.column.configs.options || {
            size: "auto",
            onText: "ON",
            offText: "OFF",
            onColor: "primary",
            offColor: "default",
        });
        options.onSwitchChange = (event, state) => {
            // $(event.target).closest(".bootstrap-switch").next().val(state ? "on" : "off").change();
            event.currentTarget.value = state ? 'on' : 'off';
            new Promise((resolve, reject) => {
                if (this.column.configs.options?.onSwitchChange instanceof Function) {
                    this.column.configs.options.onSwitchChange.call(this, event, state);
                }
                this.change(event);
            })
        }
        $(input).bootstrapSwitch(options);
        this.nodes.push(input);
        return this.insertLabel(el);
    }

    /**
     * html渲染
     * @returns {Render}
     */
    html() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        el.classList.add('field-item--' + this.column.type);
        el.classList.add(this.getName().replace(/\[|\]/mg, '-').replace(/-+$/mg, ''));
        el.innerHTML = this.column.html;
        this.$element.append(el);
        this.nodes.push(el);
        return this.makeAttributes({el}).insertLabel(el);
    }

    /**
     * 分组渲染
     * @returns {Render}
     */
    group() {
        let el = document.createElement('section');
        el.classList.add('form-grid-group');
        el.classList.add('col-24');
        this.$element.append(el);
        if (this.column.label) {
            let title = document.createElement('div');
            title.classList.add('form-grid-group-title');
            title.innerHTML = this.column.label;
            el.append(title);
        }
        let box = document.createElement('div');
        box.classList.add('form-grid-group-content');
        el.append(box);
        this.nodes.push(new Group({
            parent: this,
            columns: this.column.children || [],
            element: box,
            row: this.row,
            name: this.getName(),
            column: this.column,
            value: this.value,
            grid: this.grid,
        }))
        return this.makeAttributes({el});
    }

    /**
     * 邮箱
     * @returns {Row}
     */
    email() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        this.$element.append(el);
        let icon = document.createElement('span');
        icon.classList.add('input-group-addon');
        icon.innerHTML = `<i class="fa fa-envelope fa-fw"></i>`;
        el.append(icon);
        let input = document.createElement('input');
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.name = this.getName();
        input.value = this.value;
        input.type = 'text';
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        if (this.column.configs.verify) {
            registerEvent(input, 'change', (e) => {
                if (e.currentTarget.value) {
                    if (this.column.configs.verify instanceof Function) {
                        let error = this.column.configs.verify.call(this, {value: e.currentTarget.value, event: e});
                        if (typeof error == 'boolean') {
                            if (error) {
                                el.classList.add('error');
                                el.dataset.error = '请输入正确的邮箱';
                            } else {
                                el.classList.remove('error');
                                el.dataset.error = '';
                            }
                        } else {
                            if (error) {
                                el.classList.add('error');
                                el.dataset.error = error;
                            } else {
                                el.classList.remove('error');
                                el.dataset.error = '';
                            }
                        }
                    } else {
                        if (/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/.test(e.currentTarget.value)) {
                            el.classList.remove('error');
                            el.dataset.error = '';
                        } else {
                            el.classList.add('error');
                            el.dataset.error = '请输入正确的邮箱';
                        }
                    }
                    if (el.dataset.error) {
                        alertMsg({type: 'error', message: el.dataset.error});
                    }
                } else {
                    el.classList.remove('error');
                    el.dataset.error = '';
                }
            });
        }
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 手机号
     * @returns {Row}
     */
    phone() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        this.$element.append(el);
        let icon = document.createElement('span');
        icon.classList.add('input-group-addon');
        icon.innerHTML = `<i class="fa fa-phone fa-fw"></i>`;
        el.append(icon);
        let input = document.createElement('input');
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.name = this.getName();
        input.value = this.value;
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        if (this.column.configs.verify) {
            registerEvent(input, 'change', (e) => {
                if (e.currentTarget.value) {
                    if (this.column.configs.verify instanceof Function) {
                        let error = this.column.configs.verify.call(this, {value: e.currentTarget.value, event: e});
                        if (typeof error == 'boolean') {
                            if (error) {
                                el.classList.remove('error');
                                el.dataset.error = '';
                            } else {
                                el.classList.add('error');
                                el.dataset.error = '请输入正确的手机号';
                            }
                        } else {
                            if (error) {
                                el.classList.add('error');
                                el.dataset.error = error;
                            } else {
                                el.classList.remove('error');
                                el.dataset.error = '';
                            }
                        }
                    } else {
                        if (/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/.test(e.currentTarget.value)) {
                            el.classList.remove('error');
                            el.dataset.error = '';
                        } else {
                            el.classList.add('error');
                            el.dataset.error = '请输入正确的手机号';
                        }
                    }
                    if (el.dataset.error) {
                        alertMsg({message: el.dataset.error, type: 'error'});
                    }
                } else {
                    el.dataset.error = '';
                    el.classList.remove('error');
                }
            });
        }
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 密码输入
     * @returns {Row}
     */
    password() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        this.$element.append(el);
        let icon = document.createElement('span');
        icon.classList.add('input-group-addon');
        icon.innerHTML = `<i class="fa fa-eye-slash fa-fw"></i>`;
        el.append(icon);
        let input = document.createElement('input');
        input.classList.add('form-control');
        input.classList.add('field-item--' + this.column.type);
        input.classList.add(this.column.name);
        input.type = 'password';
        input.value = this.value;
        input.name = this.getName();
        input.placeholder = '请输入' + this.column.label;
        el.append(input);
        this.nodes.push(input);
        return this.makeAttributes({el, input}).insertLabel(el);
    }

    /**
     * 范围选择
     * @returns {Render}
     */
    range() {
        let el = document.createElement('div');
        el.classList.add('input-group');
        this.$element.append(el);
        let box = document.createElement('div');
        box.classList.add('form-control');
        box.classList.add('form-grid-range');
        el.append(box);
        let min = document.createElement('div');
        min.classList.add('start');
        let minInput = document.createElement('input');
        minInput.classList.add('form-control');
        minInput.classList.add(this.column.name);
        minInput.value = this.value ? (this.value[0] || this.value.start || '') : '';
        minInput.name = this.getName('start');
        minInput.placeholder = '最小值';
        min.append(minInput);
        let separator = document.createElement('span');
        separator.classList.add('separator');
        separator.innerHTML = this.column.configs.separator || '至';
        let max = document.createElement('div');
        max.classList.add('end')
        let maxInput = document.createElement('input');
        maxInput.classList.add('form-control');
        maxInput.classList.add(this.column.name);
        maxInput.value = this.value ? (this.value[1] || this.value.end || '') : '';
        maxInput.name = this.getName('end');
        maxInput.placeholder = '最大值';
        max.append(maxInput);
        box.append(min);
        box.append(separator);
        box.append(max);
        if (this.column.attributes.placeholder) {
            let placeholder = this.column.attributes.placeholder;
            if (typeof placeholder == 'object') {
                minInput.placeholder = placeholder.start || placeholder[0] || minInput.placeholder;
                maxInput.placeholder = placeholder.end || placeholder[1] || maxInput.placeholder;
            } else {
                minInput.placeholder = placeholder;
                maxInput.placeholder = placeholder;
            }
        }
        if (this.column.attributes.required) {
            el.insertBefore(this.makeAsterisk(), box);
        }
        if (this.column.attributes.readOnly) {
            min.setAttribute('readonly', '');
            max.setAttribute('readonly', '');
        }
        if (this.column.attributes.disabled) {
            min.setAttribute('disabled', '');
            max.setAttribute('disabled', '');
        }
        if (['date', 'time', 'datetime'].includes(this.column.configs.type)) {
            max.type = min.type = 'text';
            let icon = document.createElement('span');
            icon.classList.add('input-group-addon');
            icon.classList.add('form-grid-range-icon');
            icon.innerHTML = `<i class="fa fa-calendar fa-fw"></i>`;
            el.insertBefore(icon, box);
            let opt = this.column.configs.options || {
                "format": this.column.configs.type == 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : (this.column.configs.type == 'date' ? 'YYYY-MM-DD' : 'HH:mm:ss'),
                "locale": "zh-CN",
                "allowInputToggle": true,
            };
            datetimePicker({
                el: min,
                input: minInput,
                options: opt,
            });
            datetimePicker({
                el: max,
                input: maxInput,
                options: opt,
            });
        } else {
            maxInput.type = minInput.type = this.column.configs.type || 'number';
            if (maxInput.type == 'number') {
                let preCode = '';
                let accuracy = this.column.configs.accuracy === undefined ? 2 : this.column.configs.accuracy;
                let keyCodes = [96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 110, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 111, 190, 187, 189];
                let reg = new RegExp('(\\d|-|\\+|\\*|\\/){0,}\\.\\d{' + accuracy + ',}');
                let preg = new RegExp('(\\d|-|\\+|\\*|\\/){0,}\\.\\d{' + (accuracy + 1) + ',}');
                if (minInput.value) {
                    minInput.value = parseFloat(minInput.value).toFixed(accuracy);
                }
                if (maxInput.value) {
                    maxInput.value = parseFloat(maxInput.value).toFixed(accuracy);
                }
                const keyDown = function (e) {
                    if ((keyCodes.includes(e.keyCode) && reg.test(e.currentTarget.value)) ||
                        [106, 111, 191].includes(e.keyCode) || (preCode == 16 && e.keyCode == 56)) {
                        e.stopPropagation();
                        e.preventDefault();
                    } else if ([107, 109, 189].includes(e.keyCode) || (preCode == 16 && e.keyCode == 187)) {
                        // if (e.currentTarget.value) {
                        //     e.stopPropagation();
                        //     e.preventDefault();
                        // }
                    } else {
                        preCode = e.keyCode;
                    }
                }
                const keyUp = function (e) {
                    preCode = '';
                    if (preg.test(e.currentTarget.value)) {
                        e.currentTarget.value = parseFloat(e.currentTarget.value).toFixed(accuracy);
                    }
                }
                registerEvent(minInput, 'keydown', keyDown);
                registerEvent(minInput, 'keyup', keyUp);
                registerEvent(maxInput, 'keydown', keyDown);
                registerEvent(maxInput, 'keyup', keyUp);
            }
        }
        this.nodes.push(minInput);
        this.nodes.push(maxInput);
        this.realValue = {
            start: minInput.value,
            end: maxInput.value,
        };
        return this.makeAttributes({el}).insertLabel(el);
    }

    /**
     * 图片上传
     * @returns {Render}
     */
    image() {
        this.nodes.push(new ImageField(this));
        return this;
    }

    /**
     * 模态框
     * @returns {Render}
     */
    modal() {
        let el = document.createElement('div');
        el.classList.add('form-grid-modal');
        this.$element.append(el);
        if (['expand'].includes(this.parent?.column?.type)) {
            el.dataset.colWidth = false;
        }
        let btn = document.createElement('a');
        el.append(btn);
        let icon = document.createElement('i');
        icon.classList.add('fa');
        icon.classList.add('fa-clone');
        btn.append(icon);
        let text = document.createElement('text');
        text.innerHTML = '查看';
        btn.append(text);
        this.makeAttributes({el}).insertLabel(el);
        this.nodes.push(new Modal({
            parent: this.parent,
            column: this.column,
            row: this.row,
            value: this.value,
            name: this.getName(),
            button: btn,
            icon,
            text,
            grid: this.grid,
            el: this.$element,
        }));
        return this;
    }

    /**
     * 收缩框
     * @returns {Render}
     */
    expand() {
        let el = document.createElement('div');
        el.classList.add('form-grid-expand');
        let a = document.createElement('a');
        a.dataset.expand = 'false';
        el.append(a);
        let icon = document.createElement('i');
        icon.classList.add('fa');
        icon.classList.add('fa-angle-double-down');
        a.append(icon);
        let text = document.createElement('text');
        text.innerHTML = '展开';
        a.append(text);
        var element;
        if (['expand'].includes(this.parent?.column?.type)) {
            element = document.createElement('section');
            element.classList.add('build-in-expand');
            element.classList.add('col-24');
            this.$element.append(element);
            element.append(el);
            el.dataset.colWidth = false;
        } else {
            this.$element.append(el);
            element = this.$element;
        }
        this.makeAttributes({el}).insertLabel(el);
        this.nodes.push(new Expand({
            column: this.column,
            row: this.row,
            value: this.value,
            td: element,
            name: this.getName(),
            parent: this.parent,
            button: a,
            icon,
            text,
            grid: this.grid,
        }));
        return this;
    }

    /**
     * tab选项框
     * @returns {Render}
     */
    tab() {
        let el = document.createElement('div');
        el.classList.add('form-grid-tab');
        el.classList.add('pos-' + (this.column.configs.position || 'top'))
        this.$element.append(el);
        this.nodes.push(new Tab({
            parent: this,
            grid: this.grid,
            value: this.value,
            row: this.row,
            el,
            column: this.column,
            name: this.getName(),
        }));
        return this.makeAttributes({el});
    }

    /**
     * 内嵌grid表单
     * @returns {Render}
     */
    renderGrid() {
        let el = document.createElement('div');
        let id = this.column.name + '-' + Date.now();
        let options = this.column.options;
        el.dataset.colWidth = false;
        el.classList.add('form-inline-grid');
        el.id = id;
        this.$element.append(el);
        // todo::默认值 字段名称
        if (this.name) {
            options.name = this.name + '[' + this.column.name + ']';
        }
        this.nodes.push(new FormGrid(el, options, this));
        return this.makeAttributes({el}).insertLabel(el);
    }
}

class Modal {
    constructor({parent, column, row, value, name, button, icon, text, el, grid}) {
        this.parent = parent;
        this.column = column;
        this.row = row;
        this.value = value || {};
        this.name = name;
        this.button = button;
        this.icon = icon;
        this.text = text;
        this.$el = el;
        this.grid = grid;
        this.state = false;
        this.renders = [];
        this.realValue = JSON.parse(JSON.stringify(this.value));
        this.registerEvents();
    }

    onChange(name, value) {
        let oldValue = JSON.parse(JSON.stringify(this.realValue));
        let nameArr = name.replace(this.getName(), '').split(/\]\[|\]|\[/mg).filter(v => v);
        let lastName = nameArr.pop();
        let newValue = this.realValue;
        nameArr.forEach((n) => {
            if (!newValue[n]) {
                newValue[n] = {};
            }
            newValue = newValue[n];
        })
        newValue[lastName] = value;
        new FormEvent({
            column: this.column,
            parent: this,
            oldValue,
            newValue: this.realValue,
            type: 'change',
        });
        this.parent.onChange(this.getName(), this.realValue);

        return this;
    }

    registerEvents() {
        registerEvent(this.button, 'click', (e) => {
            if (this.state) {
                this.hide();
            } else {
                this.show();
            }
        });
        return this.render();
    }

    show() {
        this.state = true;
        this.grid.$box.append(this.$mask);
        if (this.column.content) {
            this.asyncContent();
        }
        return this;
    }

    hide() {
        this.state = false;
        this.grid.$hideElement.append(this.$mask);
        return this;
    }

    render() {
        this.$mask = document.createElement('div');
        this.$mask.classList.add('wen-modal-mask');
        this.$modal = document.createElement('section');
        this.$modal.classList.add('wen-modal');
        this.$title = document.createElement('span');
        this.$title.classList.add('wen-model-title');
        this.$title.innerHTML = this.column.title || "WEN MODAL";
        this.$header = document.createElement('div');
        this.$header.classList.add('wen-modal-header');
        this.$closeButton = document.createElement('i');
        this.$closeButton.classList.add('fa');
        this.$closeButton.classList.add('fa-close');
        this.$closeButton.classList.add('btn-close')
        this.$content = document.createElement('div');
        this.$content.classList.add('wen-modal-content');
        this.$header.append(this.$title);
        this.$header.append(this.$closeButton);
        this.$mask.append(this.$modal);
        this.$modal.append(this.$header);
        this.$modal.append(this.$content);
        this.grid.$box.append(this.$mask);
        this.renderContent();
        registerEvent(this.$closeButton, 'click', () => {
            this.hide();
        });
        registerEvent(this.$mask, 'click', (e) => {
            if (e.target.querySelector('.wen-modal')) {
                this.hide();
            }
        });
        this.grid.$hideElement.append(this.$mask);
        return this;
    }

    renderContent() {
        if (this.column.html) {
            this.$content.innerHTML = this.column.html;
        } else if (this.column.content) {
        } else {
            this.childContent();
        }
    }

    getName() {
        return this.name;
    }

    childContent() {
        if (this.column.children) {
            this.column.children.forEach((column) => {
                this.renders.push(new Render({
                    parent: this,
                    column,
                    value: column.type == 'tab' || !column.name ? this.value : (this.value ? this.value[column.name] || null : null),
                    row: this.row,
                    name: this.getName(),
                    el: this.$content,
                    label: true,
                    grid: this.grid,
                }))
            })
        }
        return this;
    }

    asyncContent() {
        if (this.$content.innerHTML) {
            return this;
        }
        if (!this.$loading) {
            this.$loading = document.createElement('div');
            this.$loading.classList.add('form-grid-async-loading');
            let icon = document.createElement('i');
            icon.classList.add('fa');
            icon.classList.add('fa-spinner');
            this.$loading.append(icon);
        }
        this.$content.append(this.$loading);
        request({
            url: this.grid.asyncRoute || '/admin/__form_grid__',
            data: {
                __key__: this.row[this.grid.keyName || 'id'],
                __class__: this.column.content,
                __type__: '__MODAL__',
            }
        }).then((res) => {
            let data = res.data;
            if (typeof data == 'string') {
                this.$content.innerHTML = data;
            } else {
                this.$content.innerHTML = data.content;
            }
            let scripts = res.response.getResponseHeader('form-scripts');
            if (scripts) {
                scripts = JSON.parse(scripts);
                Object.values(scripts).forEach((script) => {
                    if (script) {
                        if (typeof script == 'string') {
                            eval(script);
                        } else {
                            if (script.type == 'link') {
                                let scriptElement = document.createElement('script');
                                scriptElement.src = script.content;
                                this.$content.append(scriptElement);
                            } else if (script.content) {
                                eval(script.content);
                            }
                        }
                    }
                })
            }
        }).catch(err => {
            alertMsg({message: err.message || err.msg || '请求内容失败！', type: 'error'});
        }).finally(() => {
            this.$loading.remove();
        })
        return this;
    }


    refresh() {
        this.renders.forEach((vo) => {
            vo.name = this.name;
            vo.refresh();
        })
        return this;
    }
}

class Expand {
    constructor({column, row, td, name, value, parent, button, icon, text, grid}) {
        this.column = column;
        this.row = row;
        this.el = td;
        this.grid = grid;
        this.name = name;
        this.value = value || {};
        this.expand = false;
        this.parent = parent;
        this.button = button;
        this.icon = icon;
        this.text = text;
        this.renders = [];
        this.px_duration = column.configs?.duration || 0.2;
        this.realValue = JSON.parse(JSON.stringify(this.value));
        this.registerEvents();
    }


    onChange(name, value) {
        let oldValue = JSON.parse(JSON.stringify(this.realValue));
        let nameArr = name.replace(this.getName(), '').split(/\]\[|\]|\[/mg).filter(v => v);
        let lastName = nameArr.pop();
        let newValue = this.realValue;
        nameArr.forEach((n) => {
            if (!newValue[n]) {
                newValue[n] = {};
            }
            newValue = newValue[n];
        })
        newValue[lastName] = value;
        // todo::检测变更事件
        new FormEvent({
            column: this.column,
            parent: this,
            oldValue,
            newValue: this.realValue,
            type: 'change',
        });
        this.parent.onChange(this.getName(), this.realValue);
        return this;
    }

    registerEvents() {
        registerEvent(this.button, 'click', (e) => {
            if (this.expand) {
                this.hide();
            } else {
                this.show();
            }
        });
        return this.render();
    }

    show() {
        if (this.parent.activeExpand) {
            let oldExpand = this.parent.activeExpand;
            this.parent.activeExpand = this;
            oldExpand.hide();
        }
        this.expand = true;
        this.icon.classList.remove('fa-angle-double-down');
        this.icon.classList.add('fa-angle-double-up');
        this.text.innerHTML = '收起';
        this.button.dataset.expand = 'true';
        this.parent.activeExpand = this;
        if (this.parent.$activeExpandTr) {
            this.parent.$activeExpandTr.style.display = 'table-row';
        }
        this.asyncContent().finally(() => {
            let oldRect = this.parent.$activeExpand.getBoundingClientRect();
            this.parent.$expandContent.append(this.$contentElement);
            let rect = this.parent.$expandContent.getBoundingClientRect();
            let duration = Math.abs(rect.height - oldRect.height) * this.px_duration + 'ms';
            this.parent.$activeExpand.style['transition-duration'] = duration;
            this.parent.$activeExpand.style['-moz-transition-duration'] = duration;
            this.parent.$activeExpand.style['-o-transition-duration'] = duration;
            this.parent.$activeExpand.style['-webkit-transition-duration'] = duration;
            this.parent.$activeExpand.style.height = rect.height + 'px';
            this.checkParentExpand({
                parent: this.parent,
                rect,
                duration,
            });
        });
        return this;
    }

    checkParentExpand({parent, rect, duration}) {
        if (parent instanceof Modal || parent.parent instanceof Modal) {
            return this;
        }
        if (parent && parent.parent) {
            if (parent.parent.$activeExpand) {
                let rect1 = parent.parent.$expandContent.getBoundingClientRect();
                parent.parent.$activeExpand.style.height = rect1.height + rect.height + 'px';
            }
            this.checkParentExpand({
                parent: parent.parent,
                rect,
                duration,
            })
        }
        return this;
    }

    hide() {
        this.expand = false;
        this.icon.classList.add('fa-angle-double-down');
        this.icon.classList.remove('fa-angle-double-up');
        this.text.innerHTML = '展开';
        this.button.dataset.expand = 'false';
        if (this == this.parent.activeExpand) {
            if (this.parent.$activeExpand) {
                let rect = this.parent.$expandContent.getBoundingClientRect();
                this.checkParentHide({
                    parent: this.parent,
                    rect,
                });
                this.parent.$activeExpand.style.height = '0px';
            }
        } else {
            this.grid.$hideElement.append(this.$contentElement);
        }
        return this;
    }

    checkParentHide({parent, rect, duration}) {
        if (parent instanceof Modal || parent.parent instanceof Modal) {
            return this;
        }
        if (parent && parent.parent) {
            if (parent.parent.$activeExpand) {
                let rect1 = parent.parent.$activeExpand.getBoundingClientRect();
                let h = rect1.height - rect.height;
                parent.parent.$activeExpand.style.height = h + 'px';
            }
            this.checkParentHide({
                parent: parent.parent,
                rect,
                duration,
            })
        }
        return this;
    }

    render() {
        let that = this;
        if (['expand'].includes(this.parent?.column?.type)) {
            if (this.parent.$activeExpand) {
                this.parent.$activeExpand.remove();
            }
            let activeExpand = document.createElement('div');
            activeExpand.classList.add('form-grid-expand-content-box')
            this.el.append(activeExpand);
            let expandContent = document.createElement('div');
            expandContent.classList.add('form-grid-expand-row');
            activeExpand.append(expandContent);
            registerEvent(activeExpand, 'transitionend', function (e) {
                if (!that.parent.activeExpand.expand) {
                    if (that.parent.$activeExpandTr) that.parent.$activeExpandTr.style.display = 'none';
                    that.grid.$hideElement.append(that.$contentElement);
                }
            });
            this.parent.$activeExpand = activeExpand;
            this.parent.$expandContent = expandContent;
            // this.parent.$activeExpandTr = this.el;
        } else if (!this.parent.$expandContent) {
            let tr = document.createElement('tr');
            tr.classList.add('form-grid-expand-content');
            let td = document.createElement('td');
            td.setAttribute('colspan', '100%');
            tr.append(td);
            let activeExpand = document.createElement('div');
            activeExpand.classList.add('form-grid-expand-content-box')
            td.append(activeExpand);
            let expandContent = document.createElement('div');
            expandContent.classList.add('form-grid-expand-row');
            activeExpand.append(expandContent);
            registerEvent(activeExpand, 'transitionend', function (e) {
                if (!that.parent.activeExpand.expand) {
                    if (that.parent.$activeExpandTr) that.parent.$activeExpandTr.style.display = 'none';
                    that.grid.$hideElement.append(that.$contentElement);
                }
            });
            this.parent.$activeExpand = activeExpand;
            this.parent.$expandContent = expandContent;
            this.parent.$activeExpandTr = tr;
            if (this.el.parentElement.nextElementSibling) {
                this.el.parentElement.parentElement.insertBefore(tr, this.el.parentElement.nextElementSibling);
            } else {
                this.el.parentElement.parentElement.append(tr);
            }
        }
        this.$contentElement = document.createElement('div');
        this.$contentElement.classList.add('expand-content')
        this.grid.$hideElement.append(this.$contentElement);
        return this.renderContent();
    }


    getName() {
        return this.name;
        // if (this.name) {
        //     return this.name + '[' + this.column.name + ']';
        // }
        // return this.column.name;
    }

    renderContent() {
        if (this.column.html) {
            this.parent.$expandContent.innerHTML = this.column.html;
        } else if (this.column.content) {

        } else {
            this.childContent();
        }
        if (this.column.configs.expand) {
            this.show();
        }
        return this;
    }

    childContent() {

        this.column.children?.forEach((column) => {
            this.renders.push(new Render({
                parent: this,
                column,
                value: column.type == 'tab' || !column.name ? this.value : (this.value ? this.value[column.name] || null : null),
                row: this.row,
                name: this.getName(),
                el: this.$contentElement,
                label: true,
                grid: this.grid,
            }));
        });
        return this;
    }


    asyncContent() {
        return new Promise((resolve, reject) => {
            if (this.column.content) {
                if (this.$contentElement.innerHTML) {
                    resolve(true);
                    return;
                }
                if (!this.$loading) {
                    this.$loading = document.createElement('div');
                    this.$loading.classList.add('form-grid-async-loading');
                    let icon = document.createElement('i');
                    icon.classList.add('fa');
                    icon.classList.add('fa-spinner');
                    this.$loading.append(icon);
                }
                this.$contentElement.append(this.$loading);
                resolve(true);

                request({
                    url: this.grid.asyncRoute || '/admin/__form_grid__',
                    data: {
                        __key__: this.row[this.grid.keyName || 'id'],
                        __class__: this.column.content,
                        __type__: '__EXPAND__',
                    }
                }).then((res) => {
                    let data = res.data;
                    if (typeof data == 'string') {
                        this.$contentElement.innerHTML = data;
                    } else {
                        this.$contentElement.innerHTML = data.content;
                    }
                    let scripts = res.response.getResponseHeader('form-scripts');
                    if (scripts) {
                        scripts = JSON.parse(scripts);
                        Object.values(scripts).forEach((script) => {
                            if (typeof script == 'string') {
                                eval(script);
                            } else {
                                if (script.type == 'link') {
                                    let scriptElement = document.createElement('script');
                                    scriptElement.src = script.content;
                                    this.$content.append(scriptElement);
                                } else if (script.content) {
                                    eval(script.content);
                                }
                            }
                        })
                    }
                }).catch(err => {
                    alertMsg({message: err.message || err.msg || '请求内容失败！', type: 'error'});
                }).finally(() => {
                    this.$loading.remove();
                    let rect = this.parent.$expandContent.getBoundingClientRect();
                    let duration = this.parent.$activeExpand.style['transition-duration'];
                    this.parent.$activeExpand.style.height = rect.height + 'px';
                    this.checkParentExpand({
                        parent: this.parent,
                        rect,
                        duration,
                    });
                })
            } else {
                resolve(true);
            }
        });
    }

    refresh() {
        this.renders.forEach((vo) => {
            vo.name = this.getName();
            vo.refresh();
        })
        return this;
    }
}

class Row {
    constructor(grid, row, rowIndex) {
        this.id = Date.now() + '-' + Math.floor(Math.random() * 10000);
        this.parent = this.grid = grid;
        this.row = row;
        this.index = rowIndex;
        this.renders = [];
        this.realValue = row ? JSON.parse(JSON.stringify(row)) : {};
        this.render();
    }

    /**
     * 行值变化事件
     * @param name
     * @param value
     * @returns {Row}
     */
    onChange(name, value) {
        let nameArr = name.replace(this.getName(), '').split(/\[|\]\[|\]/mg).filter((v) => {
            return v;
        });
        let val = this.realValue;
        // let oldValue = JSON.parse(JSON.stringify(val));
        let lastName = nameArr.pop();
        nameArr.forEach((n) => {
            if (n) {
                if (!val[n]) {
                    val[n] = {};
                }
                val = val[n];
            }
        })
        val[lastName] = value;
        this.parent.onChange(this.getName(), this.realValue);
        return this;
    }

    /**
     * 获取基础名称
     * @returns {string}
     */
    getName() {
        return this.grid.name + '[' + this.index + ']';
    }

    /**
     * 行
     * @returns {Row}
     */
    render() {
        this.$tr = document.createElement('tr');
        this.$tr.classList.add('form-grid-row');
        this.$tr.dataset.col = this.index;
        this.grid.$body.append(this.$tr);
        this.forEach(this.grid.columns, this.row, this.getName());
        this.renderActions();
        return this;
    }

    /**
     * 刷新
     * @returns {Row}
     */
    refresh() {
        this.$tr.dataset.col = this.index;
        this.renders.forEach((renderItem, i) => {
            /** @var Render renderItem */
            renderItem.name = this.grid.name + '[' + this.index + ']';
            renderItem.refresh();
        })
        return this;
    }

    /**
     * 操作列
     * @returns {Row}
     */
    renderActions() {
        if (!this.grid.actions.disabled) {
            let td = document.createElement('td');
            td.classList.add('column-actions');
            this.$tr.append(td);
            this.grid.actions.buttons?.forEach((button) => {
                if (!button.hide) {
                    let btn = document.createElement('a');
                    btn.classList.add('btn');
                    btn.classList.add('btn-xs');
                    btn.classList.add('btn-' + (button.type || 'default'));
                    btn.classList.add('btn-' + button.id);
                    btn.classList.add('actions-button');
                    if (button.icon) {
                        let icon = document.createElement('i');
                        icon.classList.add('fa');
                        icon.classList.add(button.icon);
                        btn.append(icon);
                    }
                    if (button.text) {
                        let text = document.createElement('text');
                        text.innerHTML = button.text;
                        btn.append(text);
                    }
                    td.append(btn);
                    switch (button.id) {
                        case 'delete':
                            registerEvent(btn, 'click', (e) => {
                                this.grid.removeRow(this);
                            });
                            break;
                        case 'copy':
                            registerEvent(btn, 'click', (e) => {
                                this.grid.copyRow(this);
                            });
                            break;
                        default:
                            if (button.event) {
                                registerEvent(btn, 'click', (e) => {
                                    if (typeof button.event == "string") {
                                        eval('button.event =' + button.event);
                                    }
                                    button.event.call(this, button);
                                });
                            }
                    }
                }
            });
        }
        return this;
    }

    /**
     * 列
     * @param columns
     * @param row
     * @param name
     * @returns {Row}
     */
    forEach(columns, row, name) {
        columns.forEach((column) => {
            switch (column.type) {
                case 'hidden':
                    this.makeRender(column, row, name, this.$tr, this.getValue(column, row));
                    break;
                case 'time':
                    if (!column.configs.options) {
                        column.configs.options = {
                            "format": 'HH:mm:ss',
                            "locale": "zh-CN",
                            "allowInputToggle": true,
                        }
                    }
                    this.makeRender(column, row, name, this.makeTd(column), this.getValue(column, row));
                    break;
                case 'date':
                    if (!column.configs.options) {
                        column.configs.options = {
                            "format": 'YYYY-MM-DD',
                            "locale": "zh-CN",
                            "allowInputToggle": true,
                        }
                    }
                    this.makeRender(column, row, name, this.makeTd(column), this.getValue(column, row));
                    break;
                case 'datetime':
                    this.makeRender(column, row, name, this.makeTd(column), this.getValue(column, row));
                    break;
                case 'html':
                    this.makeRender(column, row, name, this.makeTd(column), column.html);
                    break;
                case 'group':
                    this.forEach(column.children || [], row, name);
                    break;
                default:
                    this.makeRender(column, row, name, this.makeTd(column), this.getValue(column, row));
            }
        });
        return this;
    }

    /**
     * 渲染字段
     * @param column
     * @param row
     * @param name
     * @param el
     * @param value
     * @returns {Render}
     */
    makeRender(column, row, name, el, value) {
        let render = new Render({
            parent: this,
            column,
            value,
            row,
            name,
            el,
            grid: this.grid,
        });
        this.renders.push(render);
        return render;
    }

    /**
     * 单元格
     * @param column
     * @returns {HTMLTableCellElement}
     */
    makeTd(column) {
        let td = document.createElement('td');
        td.classList.add('column--' + (column.name || column.type));
        this.$tr.append(td);
        return td;
    }

    /**
     * 获取单元格值
     * @param column
     * @param row
     * @returns {*|string}
     */
    getValue(column, row) {
        return row && row[column.name] !== undefined ? row[column.name] : '';
    }

    remove() {
        this.grid.$hideElement.append(this.$tr);
        return this;
    }

    insert() {
        this.grid.$body.append(this.$tr);
        return this;
    }

    delete() {
        this.$tr.remove();
        return this;
    }

}

class FormGrid {
    id = '';
    name = '';
    columns = [];
    data = [];
    $el = null;
    header = {
        col: 1,
    }

    pageRows = [];

    count = 20;
    page = 1;
    pages = [10, 20, 30, 50, 100];
    total = 0;
    api = null;
    query = {};
    _sort_ = null;

    rows = [];
    trashed = [];
    realValue = [];
    historyData = [];

    events = {};

    actions = {
        disabled: false,
        buttons: [{
            id: 'delete',
            icon: '',
            text: '删除',
            type: 'danger',
        }, {
            id: 'copy',
            icon: '',
            text: '复制',
            type: 'warning',
        }],
        disabledCreateButton: false,
    }

    constructor(id, opt, parent) {
        this.parent = parent;
        this.initialzation(id, opt)
    }


    initialzation(id, opt) {
        if (typeof id == 'string') {
            this.$el = document.getElementById(id);
            this.id = id;
        } else {
            this.$el = id;
            this.id = this.$el.id;
        }
        this.columns = opt.columns || [];
        this.data = opt.data || [];
        this.header.col = opt.headCol || 1;
        this.name = opt.name;
        this.api = opt.api;
        this.count = opt.count || 20;
        this.pages = opt.pages || this.pages;
        this.uploadUrl = opt.uploadUrl;
        this.token = opt.token;
        this.actions = opt.actions || this.actions;
        this.events = opt.events || {};
        this.realValue = opt.data ? JSON.parse(JSON.stringify(opt.data)) : [];
        this.disabledPagination = opt.disabledPagination;
        this.asyncRoute = opt.asyncRoute || '/admin/__form_grid__';
        this.keyName = opt.keyName || 'id';
        this.md5String = opt.md5String;
        return this.initTable();
    }

    /**
     * 初始化表格
     * @returns {FormGrid}
     */
    initTable() {
        this.$box = this.$el.querySelector('.wen-form-grid[data-id="' + this.id + '"]');
        if (!this.$box) {
            this.$box = this.createElement('div', {className: 'wen-form-grid'});
            this.$el.append(this.$box);
        }
        if (!this.disabledPagination) {
            this.$pagination = this.$el.querySelector('.form-grid-pagination[data-id="' + this.id + '"]');
            if (!this.$pagination) {
                this.pagination();
            }
        }

        this.$hideElement = this.$el.querySelector('.form-grid-hidden[data-id="' + this.id + '"]');
        if (!this.$hideElement) {
            this.$hideElement = this.createElement('div', {className: 'form-grid-hidden'});
            this.$el.append(this.$hideElement);
        }
        this.$table = this.$box.querySelector('table.form-grid-table[data-id="' + this.id + '"]');
        if (!this.$table) {
            this.$table = this.createElement('table', {className: 'form-grid-table'});
            this.$box.append(this.$table);
        }

        this.$head = this.$table.querySelector('thead.form-grid-header[data-id="' + this.id + '"]');
        if (!this.$head) {
            this.makeHead();
        } else {
            this.checkHead(this.columns);
        }
        this.$body = this.$table.querySelector('tbody.form-grid-body[data-id="' + this.id + '"]');
        if (!this.$body) {
            this.$body = this.createElement('tbody', {className: 'form-grid-body'});
            this.$table.append(this.$body);
        }
        this.$empty = this.$body.querySelector('tr.form-grid-empty[data-id="' + this.id + '"]');
        if (!this.$empty) {
            this.$body.append(this.makeEmpty());
        }
        this.$loading = this.$body.querySelector('tr.form-grid-loading-tr[data-id="' + this.id + '"]');
        if (!this.$loading) {
            this.$body.append(this.makeLoading());
        }
        return this.initData().finally(() => {
            this.renderBody();
        });
    }

    /**
     * 数据分页处理 获取请求异步数据
     * @returns {Promise<unknown>}
     */
    initData(refresh = false) {
        return new Promise((resolve, reject) => {
            let historyData = this.historyData.filter(v => !this.realValue.includes(v));
            if (historyData.length) {
                // 移除前面添加的隐藏行元素
                historyData.forEach((vo, i) => {
                    let j = this.total + i;
                    // 清除 隐藏行元素
                    if (this.rows[j]) {
                        this.rows[j].delete();
                        this.rows.splice(j, 1);
                    }
                })
            }
            if (refresh) {
                this.historyData = (this.oldDataList || this.realValue).filter(v => v.__change_state__);
            }
            if (this.api) { // 设置api，则直接从api获取数据
                request({
                    url: this.api,
                    method: 'GET',
                    data: {
                        page: this.page,
                        count: this.disabledPagination ? 0 : this.count,
                        query: this.query,
                        _sort_: this._sort_ ? {
                            column: this._sort_.name,
                            value: this._sort_.__sort__
                        } : null,
                        __form_gird_id__: this.md5String,
                    }
                }).then((resp) => {
                    let res = resp.data || {};
                    this.total = res.total;
                    let list = res.list || [];
                    if (refresh) {
                        if (this.historyData.length) {
                            // 如果查询列表中存在修改记录，则替换为已修改数据；没有，则使用查询数据
                            list = list.map((item) => {
                                let history = this.historyData.filter((vo) => {
                                    return vo.__origin_data__ == JSON.stringify(item);
                                });
                                if (history.length) {
                                    return history[0];
                                }
                                return item;
                            });
                        }
                        this.rows.forEach((row) => {
                            row.delete();
                        });
                        this.pageRows = [];
                        this.rows = [];
                        this.realValue = [];
                    } else if (historyData.length) {
                        // 如果查询列表中存在修改记录，则替换为已修改数据；没有，则使用查询数据
                        list = list.map((item) => {
                            let history = historyData.filter((vo) => {
                                return vo.__origin_data__ == JSON.stringify(item);
                            });
                            if (history.length) {
                                return history[0];
                            }
                            return item;
                        });
                    }
                    let index = (this.page - 1) * this.count;
                    list.forEach((v, i) => {
                        let dex = index + i;
                        this.realValue[dex] = v;
                    });
                    this.pagination();
                }).catch((err) => {
                    alertMsg({message: err.message || err.msg || '请求数据失败！', type: 'error'});
                }).finally(() => {
                    resolve(true);
                })
            } else {
                if (refresh) {
                    if (!this.oldDataList) {
                        this.oldDataList = [].concat(this.realValue);
                    }
                    this.realValue = this.oldDataList.filter((item) => {
                        return this.filterData(this.columns, item);
                    });
                    this.rows.forEach((row) => {
                        row.delete();
                    });
                    this.rows = [];
                    this.pageRows = [];
                    if (this._sort_) {
                        // todo::排序
                        this.realValue = this.sortData(this.realValue, this._sort_);
                    }
                }
                this.total = this.realValue.length;
                this.pagination();
                resolve(true);
            }


        });
    }

    /**
     * 列表排序
     * @returns {FormGrid}
     */
    sortData(list, column) {
        if (!list || list.length <= 1) {
            return list || [];
        }
        let midIndex = Math.floor(list.length / 2);
        let left = [], right = [];
        let midItem = list.splice(midIndex, 1)[0];
        let midV = midItem[column.name];
        for (var i = 0; i < list.length; i++) {
            let item = list[i];
            let v = item[column.name];
            switch (column.__sort__) {
                case 'desc':
                    if (v > midV) {
                        left.push(item);
                    } else {
                        right.push(item)
                    }
                    break;
                case 'asc':
                default:
                    if (v < midV) {
                        left.push(item)
                    } else {
                        right.push(item);
                    }
            }
        }
        return this.sortData(left, column).concat([midItem]).concat(this.sortData(right, column));
    }

    /**
     * 筛选列表
     * @param columns
     * @param item
     * @returns {boolean}
     */
    filterData(columns, item) {
        let isTrue = true;
        this.columns.forEach((column) => {
            if (isTrue) {
                if (column.children?.length) {
                    if (!this.filterData(column.children, item)) {
                        isTrue = false;
                    }
                }
                if (column.$filterEvent) {
                    if (!column.$filterEvent.check(item)) {
                        isTrue = false;
                    }
                }
            }

        });
        return isTrue;
    }

    /**
     * 渲染表单
     * @returns {FormGrid}
     */
    renderBody() {
        this.pageRows.forEach((row) => {
            /** @var Row row */
            if (this.trashed.includes(row)) {
                row.delete();
            } else {
                row.remove();
            }
        });
        this.pageRows = [];
        let index = (this.page - 1) * this.count;
        let maxIndex = index + this.count;
        if (this.disabledPagination) {
            index = 0;
            maxIndex = this.total;
        }
        let list = this.realValue;
        for (let i = index; i < maxIndex; index++) {
            let item = list[index];
            if (item) {
                if (!item.__origin_data__) {
                    item.__origin_data__ = JSON.stringify(item);
                }
                if (this.trashed.filter((vo) => {
                    return vo.row.__origin_data__ == item.__origin_data__;
                }).length) {
                    // 过滤删除的行
                    continue;
                }
                if (this.rows[index]) {
                    this.rows[index].insert();
                } else {
                    this.rows[index] = new Row(this, item, index);
                }
                this.pageRows.push(this.rows[index]);
            }
            i++;
        }

        if (this.historyData.length) {
            let j = 0;
            this.historyData.forEach((vo) => {
                if (this.trashed.filter((o) => {
                    return o.row.__origin_data__ == (vo.__origin_data__ || JSON.stringify(vo));
                }).length) {
                    // 过滤删除的行
                    return;
                }
                let rowIndex = this.realValue.indexOf(vo);
                if (rowIndex >= 0) {//在列表中但没有在渲染页面，则创建隐藏行元素
                    if (!this.rows[rowIndex]) {
                        this.rows[rowIndex] = new Row(this, vo, rowIndex);
                        this.rows[rowIndex].remove();
                    }
                } else {  // 已被修改，但不存在列表中的数据，则添加隐藏行元素
                    rowIndex = this.total + j;
                    j++;
                    this.rows[rowIndex] = new Row(this, vo, rowIndex);
                    this.rows[rowIndex].remove(); // 隐藏
                }
            });
        }
        if (this.pageRows.length) {
            this.$empty.remove();
        } else {
            this.$body.append(this.$empty);
        }
        this.loading(false);
        return this;
    }


    /**
     * 生成空表单
     * @returns {FormGrid}
     */
    makeEmpty() {
        this.$empty = this.createElement('tr', {className: 'form-grid-empty'});
        this.$empty.innerHTML = `<td colspan="100%" class="empty-grid" style="padding: 100px;text-align: center;color: #999999">`
            + `<svg t="1562312016538" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2076" width="128" height="128" style="fill: #e9e9e9;">`
            + `<path d="M512.8 198.5c12.2 0 22-9.8 22-22v-90c0-12.2-9.8-22-22-22s-22 9.8-22 22v90c0 12.2 9.9 22 22 22zM307 247.8c8.6 8.6 22.5 8.6 31.1 0 8.6-8.6 8.6-22.5 0-31.1L274.5 153c-8.6-8.6-22.5-8.6-31.1 0-8.6 8.6-8.6 22.5 0 31.1l63.6 63.7zM683.9 247.8c8.6 8.6 22.5 8.6 31.1 0l63.6-63.6c8.6-8.6 8.6-22.5 0-31.1-8.6-8.6-22.5-8.6-31.1 0l-63.6 63.6c-8.6 8.6-8.6 22.5 0 31.1zM927 679.9l-53.9-234.2c-2.8-9.9-4.9-20-6.9-30.1-3.7-18.2-19.9-31.9-39.2-31.9H197c-19.9 0-36.4 14.5-39.5 33.5-1 6.3-2.2 12.5-3.9 18.7L97 679.9v239.6c0 22.1 17.9 40 40 40h750c22.1 0 40-17.9 40-40V679.9z m-315-40c0 55.2-44.8 100-100 100s-100-44.8-100-100H149.6l42.5-193.3c2.4-8.5 3.8-16.7 4.8-22.9h630c2.2 11 4.5 21.8 7.6 32.7l39.8 183.5H612z" p-id="2077"></path>`
            + `</svg></td>`;
        return this.$empty;
    }

    /**
     * 生成加载动态元素
     * @returns {*}
     */
    makeLoading() {
        this.$loading = this.createElement('tr', {className: 'form-grid-loading-tr'});
        let td = this.createElement('td', {
            className: 'form-grid-loading-td', attributes: {colspan: '100%'}
        });
        let el = this.createElement('div', {className: 'form-grid-loading'});
        let i = this.createElement('i', {className: ['fa', 'fa-spinner']});
        el.append(i);
        td.append(el);
        this.$loading.append(td);
        return this.$loading;
    }

    /**
     * 检测已有表头
     * @param columns
     * @param col
     * @param row
     * @returns {FormGrid}
     */
    checkHead(columns, col = 0, row = 0) {
        let nextRow = 0;
        columns.filter(v => v.type != 'hidden').forEach((column, i) => {
            let el = this.$head.children[col].children[i + row];
            let filter = el.querySelector('.form-grid-filter');
            if (filter) {
                column.$filter = filter;
                registerEvent(filter, 'click', (e) => {
                    this.filterEvent({e, column});
                });
            }
            let sorter = el.querySelector('.form-grid-sorter');
            if (sorter) {
                column.$sorter = sorter;
                registerEvent(sorter, 'click', (e) => {
                    this.sorterEvent({e, column});
                });
            }
            let helper = el.querySelector('.form-grid-helper');
            if (helper) {
                column.$helper = helper;
                registerEvent(helper, 'mouseover', (e) => {
                    this.helperEvent({e, column, event: 'mouseover'});
                });
                registerEvent(helper, 'mouseleave', (e) => {
                    this.helperEvent({e, column, event: 'mouseleave'})
                });
            }
            switch (column.type) {
                case 'group':
                    this.checkHead(column.children, col + 1, nextRow);
                    nextRow += column.children.filter(v => v.type != 'hidden').length;
                    break;
                default:
            }
        })
        if (col === 0 && !this.actions.disabled) {
            let btn = this.$head.children[0].children[columns.filter(v => v.type != 'hidden').length]?.querySelector('.actions-create');
            if (btn) {
                this.actions.$createButton = btn;
                registerEvent(btn, 'click', (e) => {
                    this.addRow(e);
                })
            }
        }
        return this;
    }


    /**
     * 生成表头
     * @returns {FormGrid}
     */
    makeHead() {
        this.$head = this.createElement('thead', {className: 'form-grid-header'});
        this.$colgroup = this.createElement('colgroup', {});
        this.$table.append(this.$colgroup);
        this.$table.append(this.$head);
        this.checkActions(this.forEachColumns(this.columns))
        return this;
    }

    /**
     * 检测是否屏蔽Actions
     * @param tr
     * @returns {FormGrid}
     */
    checkActions(tr) {
        if (!this.actions.disabled) {
            // todo::创建操作
            let th = document.createElement('th');
            th.classList.add('column-actions');
            th.setAttribute('rowspan', this.header.col);
            let span = document.createElement('span');
            span.innerHTML = '操作';
            th.append(span);
            if (!this.actions.disabledCreateButton) {
                let btn = document.createElement('i');
                btn.classList.add('fa');
                btn.classList.add('fa-plus-square');
                btn.classList.add('actions-create');
                th.append(btn);
                this.actions.$createButton = btn;
                registerEvent(btn, 'click', () => {
                    this.addRow();
                });
            }
            tr.append(th);
        }
        return this;
    }

    /**
     * 遍历字段，返回生成表头Tr
     * @param columns
     * @param level
     * @param tr
     * @returns {null}
     */
    forEachColumns(columns, level = 1, tr = null) {
        if (!tr) {
            tr = document.createElement('tr');
            this.$head.append(tr);
        }
        let nextTr = null;
        columns.filter(v => v.type != 'hidden').forEach((column) => {
            let th = document.createElement('th');
            th.innerHTML = `<text>${column.label || column.name}</text>`;
            this.makeFilter({column, th});
            this.makeSort({column, th, sort: ''});
            this.makeHelp({column, th});
            switch (column.type) {
                case 'group':
                    th.classList.add('column-group');
                    th.setAttribute('colspan', column.colspan || 1);
                    let children = column.children.filter(vo => vo.type != 'hidden');
                    if (children.length > 0) {
                        if (!nextTr) {
                            nextTr = document.createElement('tr');
                            this.$head.append(nextTr);
                        }
                        this.forEachColumns(children, column.level, nextTr);
                    }
                    break;
                default:
                    th.classList.add('column-' + column.type);
                    th.classList.add('column-' + column.name);
                    th.setAttribute('rowspan', this.header.col - level + 1);
                    this.createColgroupItem(column);
            }
            tr.append(th);
        });
        return tr;
    }

    /**
     * 生成helper图标
     * @param column
     * @param th
     * @returns {FormGrid}
     */
    makeHelp({column, th}) {
        if (column.configs.help && !column.$helper) {
            let i = document.createElement('i');
            i.classList.add('fa');
            i.classList.add('fa-question-circle');
            i.classList.add('form-grid-helper');
            i.dataset.text = column.configs.help
            th.append(i);
            column.$helper = i;
            registerEvent(i, 'mouseover', (e) => {
                this.helperEvent({e, column, event: 'mouseover'});
            });
            registerEvent(i, 'mouseleave', (e) => {
                this.helperEvent({e, column, event: 'mouseleave'})
            });
        }
        return this;
    }


    /**
     * 排序
     * @param column
     * @param th
     * @param sort
     * @returns {FormGrid}
     */
    makeSort({column, th, sort = ''}) {
        if (!column.configs.sortable) {
            return this;
        }
        if (!column.$sorter) {
            let a = document.createElement('a');
            a.classList.add('fa');
            a.classList.add('fa-fw');
            a.classList.add('form-grid-sorter');
            th.append(a);
            column.$sorter = a;
            registerEvent(a, 'click', (e) => {
                this.sorterEvent({e, column});
            });
        }
        return this;
    }

    /**
     * 筛选
     * @param column
     * @param th
     * @returns {FormGrid}
     */
    makeFilter({column, th}) {
        if (!column.configs.filter) {
            return this;
        }
        if (!column.$filter) {
            let a = document.createElement('a');
            a.href = 'javascript:void(0);';
            a.classList.add('dropdown-toggle');
            a.classList.add('form-grid-filter');
            a.dataset.toggle = 'dropdown';
            a.innerHTML = `<i class="fa fa-filter"></i>`;
            th.append(a);
            registerEvent(a, 'click', (e) => {
                this.filterEvent({e, column})
            });
            column.$filter = a;
        }
        return this;
    }

    /**
     * 筛选事件
     * @param e
     * @param column
     */
    filterEvent({e, column}) {
        if (!column.$filterEvent) {
            column.$filterEvent = new Filter({grid: this, column, event: e});
        }
        column.$filterEvent.render();
        return this;
    }

    /**
     * 排序事件
     * @param e
     * @param column
     */
    sorterEvent({e, column}) {
        let sort = column.$sorter.dataset.sort === undefined ? 'asc' : column.$sorter.dataset.sort;
        column.$sorter.classList.remove('fa-sort-amount-desc');
        column.$sorter.classList.remove('fa-sort-amount-asc');
        column.$sorter.classList.remove('fa-sort');
        column.__sort__ = sort;
        if (this._sort_ && this._sort_ != column) {
            // 其他排序
            this._sort_.$sorter.classList.remove('fa-sort-amount-desc');
            this._sort_.$sorter.classList.remove('fa-sort-amount-asc');
            this._sort_.$sorter.classList.add('fa-sort');
            this._sort_.$sorter.dataset.sort = 'asc';
        }
        switch (sort) {
            case 'desc':
                // todo::降序
                column.$sorter.classList.add('fa-sort-amount-desc');
                column.$sorter.dataset.sort = null;
                this._sort_ = column;
                break;
            case 'asc':
                // todo::升序
                column.$sorter.classList.add('fa-sort-amount-asc');
                column.$sorter.dataset.sort = 'desc';
                this._sort_ = column;
                break;
            default:
                // todo::原顺序
                column.$sorter.classList.add('fa-sort');
                column.$sorter.dataset.sort = 'asc';
                this._sort_ = null;
        }
        this.toPage(this.page, true);
        return this;
    }

    /**
     * 帮助文本事件
     * @param e
     * @param column
     */
    helperEvent({e, column, event}) {
        if (event == 'mouseover') {
            let arrow, inner;
            if (column.$helperHover) {
                arrow = column.$helperHover.querySelector('.tooltip-arrow');
                inner = column.$helperHover.querySelector('.tooltip-inner');
            } else {
                let el = document.createElement('div')
                el.classList.add('tooltip')
                el.classList.add('fade');
                el.classList.add('right');
                el.classList.add('in');
                el.classList.add('form-grid-tooltip')
                arrow = document.createElement('div');
                arrow.classList.add('tooltip-arrow');
                el.append(arrow);
                inner = document.createElement('div');
                inner.classList.add('tooltip-inner');
                el.append(inner);
                inner.innerHTML = column.configs.help;
                column.$helperHover = el;
            }
            let rect = column.$helper.getBoundingClientRect();
            column.$helper.append(column.$helperHover);
            column.$helperHover.style.left = rect.left + rect.width + 'px';
            let rest = column.$helperHover.getBoundingClientRect();
            if (rest.width < 200 && rest.height > 35) {
                column.$helperHover.classList.remove('right');
                column.$helperHover.classList.add('bottom');
                let top = rect.top + rect.height;
                column.$helperHover.style = `top:${top}px; right:0px;`;
                inner.style = `padding-top: 10px;`;
                let r = column.$helperHover.getBoundingClientRect();
                arrow.style.left = rect.left - r.left + rect.width / 2 + 'px';
            } else {
                column.$helperHover.classList.remove('bottom');
                column.$helperHover.classList.add('right');
                let top = rect.top - rest.height / 2 + rect.height / 2;
                let left = rect.left + rect.width;
                if (top < 0) {
                    arrow.style = `top: calc(50% + ${top}px)`;
                    top = 0;
                } else {
                    arrow.style = '';
                }
                column.$helperHover.style = `left: ${left}px; top: ${top}px;`;
            }
        } else {
            column.$helperHover.querySelector('.tooltip-inner').style = ``;
            column.$helperHover.style = ``;
            column.$helperHover.remove();
        }
        return this;
    }


    /**
     * 新增列配置项
     * @param column
     * @returns {FormGrid}
     */
    createColgroupItem(column) {
        // todo:: colgroup配置检测
        let col = document.createElement('col');
        let style = column.attributes?.style;
        if (style) {
            if (typeof style == "object") {
                let str = '';
                for (let i in style) {
                    str += i + ':' + style[i] + ';';
                }
                col.style = str;
            } else {
                col.style = style;
            }
        }
        this.$colgroup.append(col);
        return this;
    }

    /**
     * 创建元素
     * @param tagName
     * @param className
     * @param attributes
     * @returns {*}
     */
    createElement(tagName, {className, attributes}) {
        let el = document.createElement(tagName);
        if (className) {
            if (typeof className == 'string') {
                el.classList.add(className);
            } else {
                Object.values(className).forEach((item) => {
                    el.classList.add(item);
                })
            }
        }
        if (attributes) {
            for (let i in attributes) {
                let val = attributes[i];
                if (typeof val == 'object') {
                    val = JSON.stringify(val);
                }
                el.setAttribute(i, val);
            }
        }
        el.dataset.id = this.id;
        return el;
    }

    /**
     * 值发生变更时触发，变更事件
     * @param name
     * @param value
     * @returns {FormGrid}
     */
    onChange(name, value) {
        let oldValue = this.realValue;
        let nameArr = name.replace(this.name, '').split(/\[|\]\[|\]/mg).filter((v) => {
            return v;
        });
        let index = Number(nameArr[0]); //+ (this.page - 1) * this.count
        value.__change_state__ = true;
        value.__change_time__ = Date.now();
        this.realValue[index] = value;
        // todo:: 事件触发检测
        new FormEvent({
            column: this,
            parent: this,
            oldValue,
            newValue: this.realValue,
            type: 'change',
        })
        this.parent?.onChange(this.name, this.realValue);
        return this;
    }


    removeRow(row) {
        this.trashed.push(row);
        // this.realValue.splice(row.index, 1);
        // this.rows.splice(row.index, 1);
        // this.rows.forEach((row, i) => {
        //     row.index = i;
        //     row.refresh();
        // });
        // this.total--;
        this.toPage(this.page);
        return this;
    }

    copyRow(row) {
        let newData = Object.assign({}, row.realValue || {});
        let index = row.index + 1;
        let newValue = [];
        let newRows = [];
        newData.__copy_state__ = true;
        newData.__copy_time__ = Date.now();
        this.realValue.forEach((val, i) => {
            if (i >= index) {
                newValue[i + 1] = val;
            } else {
                newValue[i] = val;
            }
        });
        newValue[index] = newData;
        this.realValue = newValue;
        this.rows.forEach((row, i) => {
            if (i >= index) {
                row.index = i + 1;
                row.refresh();
                newRows[i + 1] = row;
            } else {
                newRows[i] = row;
            }
        });
        this.rows = newRows;
        if (index >= (this.page - 1) * this.count + this.count) {
            this.toPage(this.page + 1);
        } else {
            this.toPage(this.page);
        }
        return this;
    }

    addRow() {
        // 在最后添加
        this.realValue[this.total] = {};
        this.total++;
        // todo::跳转到最后一页
        let lastPage = Math.ceil(this.total / this.count);
        this.toPage(lastPage);
        return this;
    }


    refresh() {
        this.rows.forEach((vo) => {
            vo.refresh();
        })
        return this;
    }

    /**
     * 分页显示 或 刷新
     * @returns {FormGrid}
     */
    pagination() {
        if (this.disabledPagination) {
            return this;
        }
        if (!this.$pagination) {
            this.$pagination = this.createElement('div', {className: ['form-grid-pagination', 'box-footer', 'clearfix']});
            this.$el.append(this.$pagination);
        }

        // 总页数
        let totalPage = Math.ceil(this.total / this.count);
        this.$pagination.innerHTML = `总共 <b id="total">${this.total}</b> 条，`;
        // 分页显示UL
        let ul = document.createElement('ul');
        this.$pagination.append(ul);
        ul.classList.add('pagination');
        ul.classList.add('pagination-sm');
        ul.classList.add('no-margin');
        ul.classList.add('pull-right');
        // 上一页按钮
        let prev = document.createElement('li');
        prev.classList.add('page-item');
        if (this.page == 1) {
            prev.classList.add('disabled');
        }
        prev.innerHTML = `<span class="page-link">«</span>`;
        this.pageEvent(prev, 'prev');
        ul.append(prev);
        // 当前分页
        let current = document.createElement('li');
        current.classList.add('page-item');
        current.classList.add('active');
        current.dataset.page = this.page;
        current.innerHTML = `<span class="page-link">${this.page}</span>`;
        this.pageEvent(current, this.page);
        ul.append(current);
        // 下一页按钮
        let next = document.createElement('li');
        next.classList.add('page-item');
        if (this.page == totalPage) {
            next.classList.add('disabled');
        }
        next.innerHTML = `<span class="page-link">»</span>`;
        this.pageEvent(next, 'next');
        ul.append(next);
        let left = 2, right = 2;
        let a = this.page + 2 - totalPage;
        if (a > 0) {
            left += a;
            right -= a;
        }
        if (this.page < 3) {
            let b = 3 - this.page;
            right += b;
            left -= b;
        }
        let ellipsis = current;
        if (this.page - 2 > 2) { //前缀页数省略号
            ellipsis = document.createElement('li');
            ellipsis.classList.add('page-item');
            ellipsis.classList.add('disabled');
            ellipsis.innerHTML = `<span class="page-link">...</span>`;
            ul.insertBefore(ellipsis, current);
        }
        if (this.page - 2 > 1) { // 第一页数
            let li = document.createElement('li');
            li.classList.add('page-item');
            li.innerHTML = `<span class="page-link">1</span>`;
            li.dataset.page = 1;
            this.pageEvent(li, 1);
            ul.insertBefore(li, ellipsis);
        }

        let nextPage = next, i = left, j = right;
        while ((this.page - i > 0 && i > 0) || (this.page + j < totalPage && j > 0) || i > 0 || j > 0) {
            if (this.page - i > 0 && i > 0) {
                let li = document.createElement('li');
                li.classList.add('page-item');
                li.innerHTML = `<span class="page-link">${this.page - i}</span>`;
                li.dataset.page = this.page - i;
                ul.insertBefore(li, current);
                this.pageEvent(li, this.page - i);
            }
            if (this.page + j <= totalPage && j > 0) {
                let li = document.createElement('li');
                li.classList.add('page-item');
                li.innerHTML = `<span class="page-link">${this.page + j}</span>`;
                li.dataset.page = this.page + j;
                ul.insertBefore(li, nextPage);
                this.pageEvent(li, this.page + j);
                nextPage = li;
            }
            i--;
            j--;
        }
        if (this.page + right < totalPage - 1) {
            let ellipsis = document.createElement('li');
            ellipsis.classList.add('page-item');
            ellipsis.classList.add('disabled');
            ellipsis.innerHTML = `<span class="page-link">...</span>`;
            ul.insertBefore(ellipsis, next);
        }
        if (this.page + right < totalPage) {
            let li = document.createElement('li');
            li.classList.add('page-item');
            li.innerHTML = `<span class="page-link">${totalPage}</span>`;
            li.dataset.page = totalPage;
            ul.insertBefore(li, next);
            this.pageEvent(li, totalPage);
        }
        // 每页显示条数
        let text = document.createElement('small');
        text.innerHTML = '显示&nbsp;';
        this.$pagination.append(text)
        let select = document.createElement('select');
        this.$pagination.append(select);
        select.classList.add('input-sm');
        select.classList.add('grid-per-pager');
        select.name = 'per-page';
        this.pages.forEach((n) => {
            let opt = document.createElement('option');
            if (n == this.count) {
                opt.setAttribute('selected', 'selected');
            }
            opt.value = n;
            opt.innerHTML = n;
            select.append(opt);
        })
        text = document.createElement('small');
        text.innerHTML = '&nbsp;条';
        this.$pagination.append(text)
        registerEvent(select, 'change', (e) => {
            this.perPageChange(e);
        });
        this.$prevPageButton = prev;
        this.$nextPageButton = next;
        return this;
    }

    /**
     * 每页显示记录数量变化事件
     * @param e
     */
    perPageChange(e) {
        let count = Number(e.currentTarget.value);
        if (this.count != count) {
            this.count = count;
            this.toPage(1);
        }
        return this;
    }

    /**
     * 每页按钮点击事件
     * @param el
     * @param page
     * @returns {FormGrid}
     */
    pageEvent(el, page) {
        registerEvent(el, 'click', (e) => {
            if (e.currentTarget.classList.value.indexOf('disabled') >= 0 || e.currentTarget.classList.value.indexOf('active') >= 0) {
                return;
            }
            // todo::请求更新数据
            if (page == 'next') {
                this.toPage(this.page + 1);
            } else if (page == 'prev') {
                this.toPage(this.page - 1);
            } else {
                this.toPage(page);
            }

        });
        return this;
    }

    /**
     * 跳转到某页面
     * @param page
     * @returns {FormGrid}
     */
    toPage(page, refresh = false) {
        this.loading(true);
        let h = setTimeout(() => {
            clearTimeout(h);
            if (this.$pagination) {
                let totalPage = Math.ceil(this.total / this.count) || 1;
                if (totalPage < page) {
                    this.page = totalPage;
                } else {
                    this.page = page;
                }
                let active = this.$pagination.querySelector('.active');
                active.classList.remove('active');
                this.$pagination.querySelector('.page-item[data-page="' + this.page + '"]')?.classList.add('active');
                if (this.page > 1) {
                    this.$prevPageButton.classList.remove('disabled');
                } else {
                    this.$prevPageButton.classList.add('disabled');
                }
                if (this.page < totalPage) {
                    this.$nextPageButton.classList.remove('disabled');
                } else {
                    this.$nextPageButton.classList.add('disabled');
                }
            }
            this.initData(refresh).finally(() => {
                this.renderBody();
                if (this.parent?.parent && this.parent.parent instanceof Expand) {
                    let parent = this.parent.parent.parent;
                    if (parent && parent.$activeExpand) {
                        let rect = parent.$expandContent.getBoundingClientRect();
                        let duration = parent.$activeExpand.style['transition-duration'];
                        parent.$activeExpand.style.height = rect.height + 'px';
                        this.parent.parent.checkParentExpand({
                            parent,
                            rect,
                            duration,
                        });
                    }
                }
            })
        });
        return this;
    }

    /**
     * 显示隐藏loading元素
     * @param value
     */
    loading(value = true) {
        if (!this.$loading) {
            this.makeLoading();
        }
        if (value) {
            this.$body.append(this.$loading);
        } else {
            this.$loading.remove();
        }
    }


    /**
     * 获取最大的z-index值
     * @returns {number}
     */
    getMaxZIndex() {
        if (!this.maxZIndex) {
            let indexArr = [];
            Array.from(document.all).forEach((item) => {
                let zIndex = Number(window.getComputedStyle(item, null).getPropertyValue("z-index"));
                if (!isNaN(zIndex)) {
                    indexArr.push(zIndex);
                }
            });
            this.maxZIndex = Math.max(...indexArr);
        }
        return ++this.maxZIndex;
    }
}


class Filter {
    constructor({grid, column, event}) {
        this.grid = grid;
        this.column = column;
        this.event = event;
        this.type = column.configs.filter.type;
        this.options = column.configs.filter.options;
        this.value = null;
        this.elements = [];
        if (this.column.$filterContent) {
            this.id = this.column.$filterContent.id;
        } else {
            this.id = 'column-filter-' + this.column.name + '-' + Math.round(Math.random() * 100000);
        }
    }

    docEvent() {
        const eventFn = (e) => {
            let outSite = true;
            let el = e.target;
            while (el) {
                if (el.id == this.id || e.target == this.event.target) {
                    outSite = false;
                    break;
                }
                el = el.parentElement;
            }
            if (outSite) {
                this.column.$filter.dataset.toggle = 'false';
                this.column.$filterContent?.remove();
                document.removeEventListener('click', eventFn);
            }
        }
        document.addEventListener('click', eventFn);
    }

    render() {
        if (!this.column.$filterContent) {
            this.makeFilter();
        }
        if (this.column.$filter.dataset.toggle == 'true') {
            this.column.$filter.dataset.toggle = 'false';
            this.column.$filterContent.remove();
            this.column.$filterContent.style = '';
        } else {
            this.column.$filter.dataset.toggle = 'true';
            this.column.$filter.parentElement.append(this.column.$filterContent);
            this.docEvent();
            let rect = this.column.$filterContent.getBoundingClientRect();
            let rect1 = this.column.$filter.parentElement.getBoundingClientRect();
            this.column.$filterContent.style = `width: ${rect.width}px;height:${rect.height}px;position:fixed;top:${rect1.top + rect1.height}px;left:${rect1.left}px;z-index:` + this.grid.getMaxZIndex();
        }
        return this;
    }

    makeFilter() {
        let el = document.createElement('ul');
        el.classList.add('form-grid-dropdown-menu');
        el.id = this.id;
        let content = document.createElement('li');
        el.append(content);
        let divider = document.createElement('li');
        divider.classList.add('divider');
        el.append(divider);
        let footer = document.createElement('li');
        footer.classList.add('text-right');
        el.append(footer);
        let btn = document.createElement('button');
        btn.type = 'button';
        btn.classList.add('btn');
        btn.classList.add('btn-sm');
        btn.classList.add('btn-flat');
        btn.classList.add('btn-primary');
        btn.classList.add('pull-left');
        footer.append(btn);
        let icon = document.createElement('i');
        icon.classList.add('fa');
        icon.classList.add('fa-search');
        btn.append(icon);
        let text = document.createElement('text');
        text.innerHTML = '搜索';
        btn.append(text);
        let resetBtn = document.createElement('span');
        resetBtn.innerHTML = `<a href="javasript:void(0)" class="btn btn-sm btn-flat btn-default"><i class="fa fa-undo"></i></a>`;
        footer.append(resetBtn);
        switch (this.type) {
            case 'in':
                let ul = document.createElement('ul');
                ul.classList.add('column-filter--select')
                content.append(ul);
                el.insertBefore(this.makeFilterItem('全选', '__all__'), content);
                el.insertBefore(divider.cloneNode(true), content);
                this.value = [];
                this.selectFilter(ul);
                break;
            case 'between':
                this.value = {start: '', end: ''}
                this.betweenFilter(content);
                break;
            case 'date':
            case 'datetime':
            case 'time':
                this.value = '';
                this.datetimeFilter(content);
                break;
            default:
                this.value = '';
                this.inputFilter(content);

        }
        registerEvent(btn, 'click', () => {
            // todo:: 搜索
            this.todoFilter();
        });
        registerEvent(resetBtn, 'click', () => {
            this.reset();
        });
        this.$searchButton = btn;
        this.$resetButton = resetBtn;
        this.column.$filterContent = el;
        return this;
    }

    reset() {
        switch (this.type) {
            case 'in':
                this.resetState = true;
                //todo::重置
                this.elements.filter((item) => {
                    return item.querySelector('.icheckbox_minimal-blue[aria-checked="true"]')
                }).forEach((vo) => {
                    vo.click();
                })
                this.resetState = false;
                break;
            case 'range':
                this.value = {
                    start: '',
                    end: '',
                }
                break;
            default:
                this.elements.value = '';
                this.value = '';
                $(this.elements).trigger('change');
        }
        this.todoFilter();
    }

    datetimeFilter(content) {
        let input = document.createElement('input');
        input.classList.add('form-control');
        input.placeholder = '请选择';
        content.append(input);
        datetimePicker({
            el: content,
            input: input,
            options: {
                "format": this.type == 'time' ? 'HH:mm:ss' : (this.type == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'),
                "locale": "zh-CN",
                "allowInputToggle": true,
            }
        });
        return this;
    }

    selectFilter(content) {
        for (let i in this.options) {
            content.append(this.makeFilterItem(this.options[i], i));
        }
        return this;
    }

    makeFilterItem(text, value) {
        let li = document.createElement('li');
        // li.dataset.value = value;
        li.classList.add('checkbox');
        li.classList.add('icheck');
        li.style.margin = 0;
        let label = document.createElement('label');
        label.style = 'width:100%;padding:3px;';
        li.append(label);
        let container = document.createElement('div');
        container.classList.add('icheckbox_minimal-blue');
        container.setAttribute('aria-checked', 'false');
        container.setAttribute('aria-disabled', "false");
        label.append(container);
        let input = document.createElement('input');
        input.classList.add('column-filter-input');
        input.type = 'checkbox';
        input.value = value;
        container.append(input);
        let ins = document.createElement('ins');
        ins.classList.add('iCheck-helpser');
        container.append(ins);
        let textEl = document.createElement('text');
        textEl.style['margin-left'] = '10px';
        textEl.innerHTML = text;
        label.append(textEl);
        registerEvent(li, 'click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            // $(input).trigger('change');
            let checked = container.getAttribute('aria-checked');
            if (checked == 'true') {
                container.classList.remove('checked');
                container.setAttribute('aria-checked', 'false');
            } else {
                container.classList.add('checked');
                container.setAttribute('aria-checked', 'true');
            }
            if (value == '__all__' && !this.resetState) {
                this.elements.filter((item) => {
                    return item.querySelector('.icheckbox_minimal-blue[aria-checked="' + checked + '"]')
                }).forEach((vo) => {
                    vo.click();
                })
            } else {
                if (checked == 'true') {
                    let index = this.value.indexOf(value);
                    if (index >= 0) {
                        this.value.splice(index, 1);
                    }
                } else {
                    this.value.push(value);
                }
            }
        });
        registerEvent(li, 'mouseover', (e) => {
            label.classList.add('hover');
            container.classList.add('hover');
        });
        registerEvent(li, 'mouseleave', (e) => {
            label.classList.remove('hover');
            container.classList.remove('hover');
        });
        this.elements.push(li);
        return li;
    }

    betweenFilter(content) {
        let isDatetime = ['datetime', 'date', 'time'].includes(this.options.type);
        let el = document.createElement('div');
        el.classList.add('column-filter--between');
        content.append(el);
        let opt = this.options.sliderMenus || (isDatetime ? [{
            text: '一周',
            value: dateRange({type: this.options.type, week: 1}),
        }, {
            text: '一月',
            value: dateRange({type: this.options.type, month: 1}),
        }, {
            text: '二月',
            value: dateRange({type: this.options.type, month: 2}),
        }, {
            text: '三月',
            value: dateRange({type: this.options.type, month: 3}),
        }, {
            text: '一年',
            value: dateRange({type: this.options.type, year: 1}),
        }] : []);
        let ul = document.createElement('ul');
        let minLi = document.createElement('li');
        minLi.classList.add('range-min');
        let minInput = document.createElement('input');
        minInput.classList.add('form-control');
        minInput.classList.add('range-input');
        minInput.placeholder = '起始值';
        minLi.append(minInput);
        ul.append(minLi);
        let divider = document.createElement('li');
        divider.classList.add('range-divider');
        divider.innerHTML = '至';
        ul.append(divider);
        let maxLi = document.createElement('li');
        maxLi.classList.add('range-max')
        let maxInput = document.createElement('input');
        maxInput.classList.add('form-control');
        maxInput.classList.add('range-input');
        maxInput.placeholder = '结束值';
        maxLi.append(maxInput);
        ul.append(maxLi);
        el.append(ul);
        registerEvent(minInput, 'blur', (e) => {
            this.value.start = e.currentTarget.value;
        });
        registerEvent(maxInput, 'blur', (e) => {
            this.value.end = e.currentTarget.value;
        });
        if (isDatetime) {
            let datetimePickerOptions = {
                "format": this.options.type == 'date' ? 'YYYY-MM-DD' : (this.options.type == 'time' ? 'HH:mm:ss' : 'YYYY-MM-DD HH:mm:ss'),
                "locale": "zh-CN",
                "allowInputToggle": true,
            }
            datetimePicker({
                el: minLi,
                input: minInput,
                options: datetimePickerOptions,
            });
            datetimePicker({
                el: maxLi,
                input: maxInput,
                options: datetimePickerOptions,
            });
        }
        if (opt.length) {
            let slider = document.createElement('ul');
            slider.classList.add('range-menu');
            opt.forEach((item) => {
                let optLi = document.createElement('li');
                optLi.classList.add('range-menu-item');
                let a = document.createElement('a');
                a.innerHTML = item.text;
                optLi.append(a);
                slider.append(optLi);
                registerEvent(a, 'click', (e) => {
                    if (item.value) {
                        this.value.start = minInput.value = item.value.start;
                        this.value.end = maxInput.value = item.value.end;
                    }
                    if (item.event) {
                        item.event = isFunction(item.event);
                        if (typeof item.event == 'function') {
                            item.event.call(this, {e, item});
                        }
                    }
                    triggerEvent(minInput, 'blur');
                    triggerEvent(maxInput, 'blur');
                })
            });
            el.insertBefore(slider, ul);
        }
        return this;
    }

    inputFilter(content) {
        // <input type="text" name="cert_name" value="" className="form-control input-sm column-filter-65e5778be7499"
        //                placeholder="关键词" autoComplete="off">
        let input = document.createElement('input');
        input.classList.add('form-control');
        input.classList.add('input-sm');
        input.classList.add('column-filter--input');
        input.placeholder = '关键词';
        input.setAttribute('autoComplete', 'off');
        content.append(input);
        registerEvent(input, 'change', (e) => {
            this.value = e.currentTarget.value;
        });
        registerEvent(input, 'keydown', (e) => {
            if (e.keyCode == 13) {
                e.preventDefault();
                e.stopPropagation();
                this.$searchButton.dispatchEvent(new Event('click'));
            }
        })
        this.elements = input;
        return this;
    }

    todoFilter() {
        let query = null;
        this.column.$filterContent.remove();
        this.column.$filter.dataset.toggle = 'false';
        switch (this.type) {
            case 'in':
                if (this.value.length) {
                    this.column.$filter.classList.add('text-yellow');
                    query = this.value;
                } else {
                    this.column.$filter.classList.remove('text-yellow');
                }
                break;
            case 'between':
                if (this.value.start || this.value.end || this.value.start === 0 || this.value.end === 0) {
                    query = this.value;
                    this.column.$filter.classList.add('text-yellow');
                } else {
                    this.column.$filter.classList.remove('text-yellow');
                }
                break;
            default:
                if (this.value) {
                    query = this.value;
                    this.column.$filter.classList.add('text-yellow');
                } else {
                    this.column.$filter.classList.remove('text-yellow');
                }
        }
        if (query) {
            if (this.grid.query[this.type]) {
                this.grid.query[this.type][this.column.name] = query;
            } else {
                this.grid.query[this.type] = {}
                this.grid.query[this.type][this.column.name] = query;
            }
        } else if (this.grid.query[this.type]) {
            delete this.grid.query[this.type][this.column.name];
            if (!Object.keys(this.grid.query[this.type])) {
                delete this.grid.query[this.type];
            }
        }
        this.grid.toPage(1, true);
    }

    /**
     * 筛选
     * @param item
     */
    check(item) {
        if (this.options.searchFn) {
            this.options.searchFn = isFunction(this.options.searchFn);
            if (typeof this.options.searchFn == 'function') {
                return this.options.searchFn.call(this, {item, value: this.value});
            }
        }
        let value = this.getItemValue(item);
        switch (this.type) {
            case 'in':
                if (this.value.length) {
                    return this.value.filter((v) => {
                        return this.options[v] == value;
                    }).length;
                    // return value && this.value.includes(value.toString())
                }
                break;
            case 'between':
                if (this.value.start || this.value.end || this.value.start === 0 || this.value.end === 0) {
                    if ((this.value.start || this.value.start === 0) && (this.value.end || this.value.end === 0)) {
                        return ((value && value >= this.value.start) || (!value && this.value.start <= 0)) && ((value <= this.value.end || (!value && this.value.end >= 0)));
                    } else if (this.value.start || this.value.start === 0) {
                        return (this.value.start <= 0 && !value) || (value && value >= this.value.start);
                    } else {
                        return value <= this.value.end || (!value && this.value.end >= 0);
                    }
                }
                break;
            case 'date':
            case 'datetime':
            case 'time':
                if (this.value) {
                    if (value) {
                        if (typeof value == 'number') {
                            value = value * 1000;
                        }
                        let date = new Date(value);
                        if (this.type == 'time') {
                            let vDate = new Date(date.toLocaleDateString() + ' ' + this.value);
                            return vDate.toLocaleTimeString() == date.toLocaleTimeString()
                        } else if (this.type == 'date') {
                            let vDate = new Date(this.value);
                            return vDate.toLocaleDateString() == date.toLocaleDateString();
                        } else {
                            let vDate = new Date(this.value);
                            return vDate.toLocaleString() == date.toLocaleString();
                        }
                    } else {
                        return false;
                    }
                }
                break;
            default:
                if (this.value) {
                    if (value) {
                        let keys = this.value.split(/\s+|,+|，+|\n+|\r+|\t+/).filter((v) => v);
                        return keys.filter((key) => {
                            return value.indexOf(key) >= 0;
                        }).length > 0;
                    } else {
                        return false;
                    }
                }
        }
        return true;
    }

    getItemValue(item) {
        return item[this.column.name];
    }
}
